Skip to content

feat(live): add visual input, proactive assistance and memory - #11369

Merged
LaZzyMan merged 9 commits into
mainfrom
feat/qwen-live-enhancements
Sep 11, 2026
Merged

feat(live): add visual input, proactive assistance and memory#11369
LaZzyMan merged 9 commits into
mainfrom
feat/qwen-live-enhancements

Conversation

@wangxiongts

@wangxiongts wangxiongts commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Extends the standalone Qwen Live experience with selectable Screen/Camera input and On Demand/Live Feed capture, isolated text-only DashScope Proactive monitors with ordered foreground delivery, and local multi-library Memory. The init wizard supports English and Simplified Chinese and asks whether to enable Memory and which consolidation model to use. The Host adds persistent draggable controls, camera preview, language/theme settings, clear input/output mute status, and a compact Subagents view for monitors and delegated harness work.

The extension upgrades the paired Host protocol to v9 with epoch/output identity and end-of-stream playback acknowledgement, preserves the original Appshot tool-continuation flow, and keeps background harness status updates alive after a voice call ends. It adds privacy-conscious diagnostics and improves audio-device lifecycle handling: muting input releases capture devices, while 24 kHz provider PCM is resampled by Web Audio using the default output-device clock. Migration retains the upstream playback IPC trust checks, real receipt wiring and newer built-in serve ownership/readiness checks.

Settings also provides Open config.json ↗ to open the connected standalone daemon's actual configuration in the OS-associated JSON editor or IDE, including custom data directories. The action is bilingual, handles unavailable files and native-open failures, and does not create or overwrite configuration. Save manual edits and restart Qwen Live to apply them.

Why it's needed

Merged M5 provides onboarding and playback receipts but does not provide configurable visual input, proactive observation, reusable local memory or a unified desktop interface for these capabilities. This follow-up integrates those workflows while keeping user actions, provider responses, background work and task-notification delivery separately owned. It also addresses concrete usability and reliability issues found during the work: stale playback receipts, cancelled-notification ordering, hidden or sticky task panels, unsafe panel placement, mute state visibility and Memory preference updates on BOM-encoded configuration files.

This is submitted as one coordinated change because the daemon and Host share an upgraded protocol, visual-input configuration, playback receipts and background-task lifecycle. The controls, Proactive delivery and Memory integration rely on those same boundaries, so landing the paired implementation and its regressions together avoids an intermediate mismatched Host/daemon contract. The size and cross-package scope still warrant maintainer review.

Reviewer Test Plan

How to verify

  1. Build both the repository and the standalone Host. Use an isolated Live data directory and a matching v9 Host. Walk through init in English and Simplified Chinese; decline or enable Memory and confirm model/library settings are preserved.
  2. Select Screen or Camera and switch between On Demand and Live Feed. Confirm permissions are requested for the selected source, camera preview can be hidden without changing the source, and On Demand retains the normal Appshot receipt continuation rather than committing audio or switching VAD modes.
  3. Create repeated Proactive conditions or timers, queue several notifications while foreground speech is active, interrupt one, and verify ordered delivery resumes without granting synthetic responses authority to operate ordinary tools. Finish the call and confirm monitors stop while harness task status can continue updating.
  4. Toggle Memory and visual observation, rename a library, and verify selection/creation/model changes require the call to end. Restart with the same isolated data directory and confirm persistence. A configuration containing a UTF-8 BOM must support Memory updates without losing unrelated fields.
  5. Move the orb and Settings, reopen them, switch language/theme, and inspect Subagents list/details. Expanded task panels must remain until explicitly closed, Back must not jump, transient summaries must hide after leaving, and panels must avoid the lower status bar. Input/output mute indicators remain visible without hovering; cancelled monitors count as completed while their detail still says Cancelled.
  6. Check playback IPC sender/epoch/output-id rejection, completion only after all audio frames and the end marker, interrupted Proactive FIFO ordering, same-library Memory serialization and the built-in serve ownership/nonce boundaries with the automated suites. Bluetooth headset microphone activation is a separate hardware profile issue; test coexistence manually with a built-in/USB microphone and Bluetooth output if relevant.

Evidence (Before & After)

Before migration, an isolated replay against the saved local implementation accepted both forged playback IPC receipts from a foreign renderer; the migrated implementation rejects both and still accepts both legitimate receipts. The end-to-end in-memory receipt replay also verifies that pending speech waits for actual playback drain, stale identities cannot release the queue and Proactive events resume in FIFO order.

Before the Memory audit fix, the configuration loader accepted a UTF-8 BOM but saving Memory preferences threw a JSON parse error. The regression now passes and verifies that unrelated configuration fields, private file mode and atomic replacement are preserved.

Current built-renderer browser inspection used synthetic state with device permissions disabled: English/Chinese, light/dark, both mute indicators, Settings, Memory rename draft across language/theme changes, Esc dismissal and Subagents placement were checked. The 248×32 status bar contained both mute indicators and the expanded 330×430 panel had zero measured overlap with the orb, controls and status bar. This is browser-fixture evidence, not a claim of native macOS hardware or real provider E2E validation. No screenshots or recordings are attached to this PR.

Automated results on the migrated source: Qwen Live 834 passed and 2 manual tests skipped; Host 258 passed; built-in serve Live 185 passed; eight fake-provider/ACP integration files 17 passed. Repository build, repository typecheck, CLI bundle, standalone Host build/typecheck, scoped ESLint/Prettier, npm/pnpm lock checks, frozen offline pnpm lock validation and desktop isolation checks passed. Independent scoped reviews covered runtime/tool authority, Memory and migration boundaries; the BOM issue was fixed and re-reviewed.

Tested on

OS Status
🍏 macOS ✅ Automated builds/tests and isolated browser UI; native media not exercised
🪟 Windows ⚠️ Not tested
🐧 Linux ⚠️ Not tested

Environment (optional)

macOS, Node.js 25.3.0, npm 11.7.0. pnpm 11.24.0 was used only to regenerate/validate its lockfile; npm remains the build/test path. Heavy builds and test suites were serialized. Networked integration used local fake providers/ACP fixtures, not paid services or user accounts. Two real-Qoder manual tests were deliberately skipped.

Risk & Scope

  • Main risk or tradeoff: this is a large cross-package feature and warrants maintainer review of protocol, background lifecycle and local persistence boundaries. Proactive and Memory are configurable and default on; actual enabled model work can consume API quota. Memory persists dialogue-derived information locally, while selected evidence/context is sent to the configured model service.
  • Not validated / out of scope: real DashScope audio/video behavior, native permission prompts, physical Bluetooth coexistence, Windows/Linux, notarization and installers. Release and npm publication are not part of this PR. Existing repository bundle-size/Browserslist warnings are not addressed here.
  • Breaking changes / migration notes: pair the daemon with the updated v9 Host; a released older Host is not a substitute for building the matching source. Standalone Memory requires Node >=22.13. Built-in qwen serve remains screen-only and does not acquire standalone Memory, subagent or shutdown authority. Preserve existing configuration and memory data when upgrading. The local base snapshot is main 078b924989; this change does not replay the already-squashed M5 history.

Linked Issues

Follow-up to #10769; related to the Live split roadmap #10118. No issue is automatically closed by this PR.

中文说明

本 PR 的改动

为独立 Qwen Live 增加可选择的屏幕/摄像头输入、按需截图/实时画面模式、隔离且仅输出文本的 DashScope Proactive 监控和按序前台播报,以及本地多记忆库 Memory。初始化向导支持英文和简体中文,并询问是否启用 Memory 及使用哪个整理模型。Host 增加记忆位置的可拖动控件、摄像头预览、语言/主题设置、明确的输入/输出静音提示,以及展示监控和委托后台任务的紧凑子智能体面板。

配套 Host 协议升级至 v9,使用通话代际、输出标识及流结束标记确认播放完成;保留原有 Appshot 工具续接流程,并允许语音通话结束后继续更新后台 harness 状态。新增注重隐私的运行诊断并改进音频设备生命周期:输入静音会释放采集设备,24 kHz 模型 PCM 则由 Web Audio 使用默认输出设备时钟重采样。迁移保留上游的播放 IPC 信任校验、真实回执接线,以及内置 serve 更新后的归属/就绪检查。

设置还新增 打开 config.json ↗ 入口,通过系统为 JSON 文件关联的 IDE/文本编辑器打开当前独立 daemon 实际使用的配置,支持自定义数据目录。入口支持中英文,并处理文件不可用或原生打开失败,不创建或覆盖配置。手动保存后需重启 Qwen Live 才生效。

为什么需要

已合并的 M5 提供初始化引导和播放回执,但尚未提供可配置视觉输入、主动观察、可复用本地记忆及承载这些能力的统一桌面界面。本扩展将它们整合,同时分别管理用户操作、模型响应、后台工作和任务通知投递。也修复开发中发现的具体问题,包括过期播放回执、取消通知的顺序、任务面板消失或常驻、面板位置不安全、静音状态不可见,以及 BOM 编码配置的 Memory 设置保存失败。

本次作为一个配套变更提交,因为 daemon 与 Host 共享升级后的协议、视觉输入配置、播放回执和后台任务生命周期。桌面控件、Proactive 投递及 Memory 集成依赖相同的边界,将配套实现与回归测试一起合入,可以避免中间版本出现 Host/daemon 契约不匹配。其规模与跨包范围仍需要维护者审查。

审查者验证方法

  1. 构建仓库和独立 Host,使用隔离 Live 数据目录及配套 v9 Host。分别走一遍中英文 init,拒绝或启用 Memory,确认模型/记忆库设置被保留。
  2. 切换屏幕/摄像头与按需截图/实时画面。确认权限只针对当前来源,隐藏预览不改变来源,按需截图仍使用正常 Appshot 回执续接,不提交音频或切换 VAD 模式。
  3. 创建重复 Proactive 条件或计时提醒,在前台播报时排入多个通知,中断其中一次,确认按序恢复且合成响应不会获得普通工具操作权限。结束通话后监控停止,后台 harness 状态仍可继续更新。
  4. 开关 Memory/视觉观察、改名记忆库,确认切换/新建库及修改模型需先结束通话。使用相同隔离目录重启验证持久化。带 UTF-8 BOM 的配置应能更新 Memory 且不丢失无关字段。
  5. 拖动、重新打开小球及设置,切换语言/主题,查看子智能体列表/详情。展开面板必须保持至显式关闭,返回不跳位,临时摘要在移走后收起,面板避开下方状态条。无需悬停即可看到输入/输出静音;取消的 monitor 计入已完成,详情仍显示已取消。
  6. 用自动化测试核对播放 IPC 的来源/代际/输出标识校验、全部音频与结束标记之后才确认完成、被中断 Proactive 的 FIFO 顺序、同库 Memory 串行和内置 serve 的归属/nonce 边界。蓝牙耳机麦克风引起的模式切换是独立硬件问题;如需验证音乐共存,应手动测试内置/USB 麦克风配蓝牙输出。

前后证据

迁移前,对保存的本地实现进行隔离回放时,外部 renderer 的两类伪造播放回执均被接受;迁移后的实现全部拒绝,同时合法回执仍全部接受。内存中的完整回执链路还验证了排队语音等待真实播放结束、过期标识无法释放队列及 Proactive 按 FIFO 恢复。

Memory 审计修复前,配置加载器能接受 UTF-8 BOM,但保存设置时发生 JSON 解析错误。新增回归测试现已通过,并检查无关字段、私有文件权限和原子替换被保留。

对当前构建产物的浏览器检查使用合成状态并禁用设备权限,检查了中英文、深浅主题、双静音提示、设置、切换语言/主题后的 Memory 改名草稿、Esc 关闭和子智能体位置。248×32 状态条完整显示两项静音,330×430 展开面板与小球、控件和状态条的测量重叠均为零。这是浏览器测试页面证据,不代表原生 macOS 硬件或真实模型端到端验证。本 PR 未附截图或录屏。

迁移后自动化结果:Qwen Live 834 项通过、2 项手动测试跳过;Host 258 项通过;内置 serve Live 185 项通过;8 个模拟 provider/ACP 集成测试文件共 17 项通过。仓库构建、仓库类型检查、CLI 打包、独立 Host 构建/类型检查、改动范围 ESLint/Prettier、npm/pnpm 锁文件检查、pnpm 离线冻结锁验证和桌面依赖隔离检查均通过。独立审查覆盖运行时/工具权限、Memory 和迁移边界,BOM 问题已修复并重新审查。

测试平台与环境

macOS 上完成自动化构建/测试和隔离浏览器 UI 检查,没有开启原生音视频;Windows 和 Linux 未验证。环境为 Node.js 25.3.0、npm 11.7.0;pnpm 11.24.0 仅用于生成/验证锁文件,构建测试仍使用 npm。重型构建与测试串行执行。涉及网络的集成测试只连接本地模拟 provider/ACP,不调用付费服务或用户账号。两项真实 Qoder 手动测试按预期跳过。

风险与范围

  • 主要风险或取舍:这是一个较大的跨包功能提交,需要维护者审查协议、后台生命周期和本地持久化边界。Proactive 与 Memory 可配置且默认开启,实际启用的模型工作会消耗 API 配额。Memory 在本地保存从对话整理的信息,选中的证据/上下文会发送到配置的模型服务。
  • 未验证/不在范围内:真实 DashScope 音视频、原生授权弹窗、蓝牙硬件共存、Windows/Linux、公证及安装包。Release 和 npm 发布不属于本 PR 的操作范围。仓库已有的包体积/Browserslist 警告未在这里处理。
  • 兼容与迁移:daemon 需配套更新后的 v9 Host,已发布的旧 Host 不能替代本分支对应源码构建。独立 Memory 要求 Node >=22.13。内置 qwen serve 仍只使用屏幕,不获得独立 Memory、子智能体或关闭 daemon 权限。升级时保留现有配置与记忆数据。本次基于 main 的 078b924989,不重放已 squash 合并的 M5 历史。

关联议题

这是 #10769 的后续扩展,与 Live 拆分路线图 #10118 相关。本 PR 不会自动关闭任何 issue。

@wangxiongts

Copy link
Copy Markdown
Collaborator Author

Local verification report

Verified feature source: c35601e89b01050ff093914a88ba3bdc6decfa2f, prepared on upstream main 078b924989. Submission-time merge simulation against main d87ccc6579 is conflict-free; the newer main commits were not rebased into the tested feature branch.

Test group Result
Qwen Live 834 passed; 2 real-backend manual tests skipped
Live Host 258 passed
Built-in serve Live 185 passed
Eight Live M1/M2/M4 fake-provider/ACP integration files 17 passed

Total: 1,294 passed, with the 2 manual tests not counted as passes. Tests ran on macOS with Node.js 25.3.0 and npm 11.7.0, using one worker/concurrency slot for heavy suites.

Passed: repository build/typecheck, CLI bundle, standalone Host build/typecheck, scoped ESLint/Prettier, npm/pnpm lock integrity, pnpm 11.24 frozen offline lock validation, desktop isolation and whitespace/conflict-marker checks. Independent exact-source probes also checked playback sender trust, epoch/output-id fencing, end-marker completion, Proactive FIFO, ordinary versus synthetic tool authority, built-in runtime ownership and Memory serialization/late-write behavior.

Browser inspection used the built renderer with synthetic state and device permissions disabled. It checked bilingual mute indicators, Settings, theme/language changes, Memory rename draft retention, Esc dismissal and Subagents placement. Real provider audio/video, native permission prompts, Bluetooth coexistence, Windows/Linux and release/notarization are not verified. Existing non-fatal bundle-size/Browserslist warnings were not hidden or represented as fixes.

中文

本地验证基于功能提交 c35601e89b,其主线基线为 078b924989;提交前与最新主线 d87ccc6579 的合并模拟无冲突,但没有将后续主线提交变基进已测试的功能分支。

1,294 项测试通过:Live 834、Host 258、内置 serve Live 185、模拟 provider/ACP 集成 17;另有 2 项真实后端手动测试跳过,不计入通过数。仓库构建/类型检查、CLI 打包、独立 Host 构建/类型检查、改动范围 lint/格式、双锁文件、离线冻结锁及桌面依赖隔离检查均通过。

独立源码探针验证了播放信任边界、代际/输出标识、结束标记、Proactive 队列、工具权限、内置运行时归属和 Memory 串行/迟到写入。浏览器 UI 检查使用构建产物与合成状态;真实音视频、原生授权、蓝牙、Windows/Linux、公证及发布尚未验证。现有非阻断的包体积/Browserslist 警告未被隐藏。

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

🚫 Qwen Triage was cancelledview run. The run was cancelled before finishing. Check for a newer run before re-running.

🚫 Qwen Triage 已取消 —— 查看运行。运行未完成即被取消。重跑前请先确认是否有更新的运行。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR — this is a substantial piece of work and the description is genuinely thorough, which made the gate pass a lot easier than it usually is.

Template looks good ✓ — every required heading is present, including the Chinese translation and a real reviewer test plan.

Problem: this is a roadmap milestone rather than a bug fix, and the direction is well-anchored: it follows the merged M5 (#10769) and sits under the open Live-split roadmap issue #10118, both authored by a maintainer. It also carries several concrete, observed defects with before/after described (the UTF-8 BOM config-save failure, stale playback receipts, cancelled-notification ordering). No theoretical-hardening concern here.

Direction: aligned with the roadmap, with one exception I can't resolve from the diff and that needs a maintainer's call. The Host now declares NSCameraUsageDescription and adds com.apple.security.device.camera to build/entitlements.mac.plist. But .github/workflows/live-host.yml — which this PR does not touch — asserts the opposite on two counts: it lists NSCameraUsageDescription among the unused permissions that must be absent from the packaged app, and assert_live_host_entitlements pins the entitlement set to exactly three keys (allow-jit, allow-unsigned-executable-memory, device.audio-input), so a fourth fails on length alone. That guard reads like a deliberate least-privilege posture for the packaged desktop app, not an accident. Adding camera capture is a reasonable feature, but widening a sandboxed app's macOS permission surface is a product/security decision, so I'm flagging it rather than judging it. This is also why Live Host (macos-latest) is currently red — more in the code review comment.

Size: core paths are touched, so here is the breakdown. The change is cross-package (packages/qwen-live, packages/live-host, packages/cli) and includes packages/qwen-live/src/tools/**, which puts it squarely under Stage 0.

Category Lines Files
Production logic 16,509 100
Tests 15,223 81
Docs / design notes 1,780 14
Lockfiles 732 3
Generated / schema 0 0

Two caveats on that table. First, it is an undercount: GitHub suppressed the diff for 41 files (they come back with no patch and 0/0), and those 41 include the whole of proactive/, subagents/, permissions/permission-broker.ts, tools/definitions.ts, tools/handles.ts, orchestrator/live-session.ts and the renderer panels — i.e. the security-critical core of the feature. The PR-level total is +52,693 / −2,138. Second, feat-type PRs are not hard-blocked on size, but at well over 500 production lines this escalates to the maintainer for awareness, and the 1000+ large-PR advisory applies too.

Approach: scope is the part I'd push back on. The PR adds twelve new design docs under docs/design/, dated 2026-09-02 through 2026-09-08, each one a separate milestone — visual input, proactive, audit fixes, memory, orb refinements, status layout, orb interaction, language and settings, subagents, subagents refinement, device and status, upstream migration — and they land as two squashed commits. Your argument for shipping as one change is that the daemon and Host share the v9 protocol, and for the protocol / visual-input / playback-receipt slice I think that argument holds: splitting those really would create an intermediate mismatched contract. But it doesn't obviously extend to memory, i18n, orb dragging, theme settings, or the status-bar layout, none of which are coupled to the wire protocol. Those look independently landable, and landing them separately would shrink the review surface a lot and make any one of them revertable on its own. Worth considering for the next milestone in this series at minimum — raising it as a question, not blocking on it.

Risk: no match against the repo's revert-correlated high-risk paths (no geminiChat, shellExecutionService, mcp-client, sandbox.ts, ACP connection, etc.), so no elevated signal from that analysis. The real risk is the two things above — the permission-surface change and the sheer size — compounded by the fact that the suppressed-diff files are the ones carrying the tool-authority boundary.

Because this is a core-path change far past the escalation threshold, I'm not going to approve it from the gate — it caps at a maintainer decision regardless of how the code review reads. Moving on to code review now, and I'll escalate with concrete findings. 🔍

中文说明

感谢贡献 —— 这是一个体量很大的改动,PR 描述写得非常扎实,让准入检查比平时顺利很多。

模板完整 ✓ —— 所有必需小节都在,包括中文翻译和真实可执行的审查者测试计划。

问题: 这是路线图里程碑而非 bug 修复,方向有明确依据:它承接已合并的 M5(#10769),并归属于仍然开放的 Live 拆分路线图 issue #10118,两者都由维护者发起。同时它带了几个已观测到的具体缺陷并给出前后对比(UTF-8 BOM 配置保存失败、过期播放回执、取消通知的顺序问题)。不存在"理论性加固"的疑虑。

方向: 与路线图一致,但有一处我无法从 diff 判断、需要维护者定夺的问题。Host 现在声明了 NSCameraUsageDescription,并在 build/entitlements.mac.plist 中加入 com.apple.security.device.camera。而本 PR 没有改动的 .github/workflows/live-host.yml 明确断言了相反的结论,且有两处:它把 NSCameraUsageDescription 列入打包产物中必须不存在的"未使用权限";assert_live_host_entitlements 又把权限集合固定为恰好三项allow-jitallow-unsigned-executable-memorydevice.audio-input),因此多出第四项仅凭数量就会失败。这个守卫看起来是对打包桌面应用的刻意最小权限约束,而不是偶然写成这样。增加摄像头能力本身合理,但放宽一个沙箱化 macOS 应用的权限面属于产品/安全决策,所以我只做标记、不做判定。这也正是 Live Host (macos-latest) 当前失败的原因 —— 详见代码审查评论。

规模: 触及核心路径,因此给出拆分。本次改动跨包(packages/qwen-livepackages/live-hostpackages/cli)并包含 packages/qwen-live/src/tools/**,完全落在 Stage 0 范围内。

类别 行数 文件数
生产逻辑 16,509 100
测试 15,223 81
文档/设计说明 1,780 14
锁文件 732 3
生成/schema 0 0

关于上表有两点说明。第一,这是低估值:GitHub 对 41 个文件省略了 diff(返回时没有 patch 且为 0/0),而这 41 个恰好包含整个 proactive/subagents/permissions/permission-broker.tstools/definitions.tstools/handles.tsorchestrator/live-session.ts 以及各 renderer 面板 —— 也就是本功能最关键的安全核心。PR 级总量为 +52,693 / −2,138。第二,feat 类型不会因为体量被硬性拦截,但生产逻辑远超 500 行,因此升级给维护者知悉,同时适用 1000+ 行大 PR 建议。

方案: 我最想提出的是范围问题。本 PR 在 docs/design/ 下新增了 12 份设计文档,日期从 2026-09-02 到 2026-09-08,每份都是一个独立里程碑 —— 视觉输入、proactive、审计修复、memory、小球交互优化、状态栏布局、小球交互、语言与设置、子智能体、子智能体细化、设备与状态、上游迁移 —— 最终以两个 squash 提交落地。你关于"作为一个配套变更提交"的理由是 daemon 与 Host 共享 v9 协议;对协议/视觉输入/播放回执这部分我认为成立:拆开确实会造成中间版本契约不匹配。但这个理由未必覆盖 memory、i18n、小球拖动、主题设置和状态栏布局,它们都与线上协议无关。这些看起来可以独立合入,分开落地能大幅缩小审查面,也让其中任何一项都能单独回滚。至少值得在这一系列的下一个里程碑中考虑 —— 我把它作为问题提出,不作为阻塞项。

风险: 与本仓库"高回滚相关性路径"清单没有命中(不涉及 geminiChatshellExecutionServicemcp-clientsandbox.ts、ACP 连接等),因此该分析没有给出升级信号。真正的风险是上面两点:权限面变更,以及体量本身 —— 并且被一个事实放大:diff 被省略的那些文件,正是承载工具权限边界的部分。

由于这是一次远超升级阈值的核心路径改动,准入关卡不会批准它 —— 无论代码审查结论如何,它都只能由维护者决定。现在进入代码审查,我会带着具体结论升级。🔍

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at c35601e89b01050ff093914a88ba3bdc6decfa2f · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head bfecd83, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 12 scenario(s).

Qwen Code · serve A/B

@wangxiongts

wangxiongts commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up: open the active Live configuration

Feature commit: d40cb2d3e89e624f3a880e097e007d04939d883c.

Settings now has a bilingual Open config.json ↗ action. It asks the OS-associated JSON editor/IDE to open the connected standalone daemon's actual configuration, including a custom data directory. It does not create or overwrite files, and manual edits take effect after restarting Qwen Live. Older/built-in daemon connections show a disabled action instead of guessing another configuration path.

The renderer can invoke only a no-argument action; the authenticated daemon's path stays in the Host main process. The action checks the current renderer/connection/Quit state and regular-file target, and surfaces native-open errors without exposing private filesystem details. The recovery hint uses the verified standalone command qwen-live init.

Verification

The maintainer's merge of main (58686a66b4) was fast-forwarded locally before final verification; none of its 148 updated paths overlapped this feature. The pending feature diff was preserved byte-for-byte. On that integrated base plus this change:

  • 116 focused tests passed: Host 79, Qwen Live 37. These are overlapping follow-up suites, not 116 additional tests to add to the previous report's total.
  • Repository build/typecheck/CLI bundle and standalone Host build/typecheck passed. Scoped lint/format, npm/pnpm lock integrity, desktop isolation and whitespace checks passed.
  • Baseline renderer probe went from missing the action to one click invoking one opener. An independent AST check confirmed that preload forwards no path argument; native IPC tests used real temporary files with a stub OS opener.
  • The actual built renderer was checked in a browser with synthetic state: English/light and Chinese/dark, success/pending/error/retry/unsupported states, final Language/Theme order, permanent scrollbar and Esc dismissal. A pointer drag from the left to the right edge kept the panel and action inside the synthetic desktop/frame with no horizontal overflow. Opening the action did not emit drag events.

No real user configuration, media, model service or OS editor was opened for verification. Native window-manager/editor launch, Windows/Linux, a clean dependency install and the full repository test suite were not exercised. Existing dependency installs were reused; upstream's root Playwright pin changed to 1.61.1 while the installed root copy is 1.58.2, and these focused tests/CUA checks do not invoke that package. No dependency or lockfile changes are introduced by this follow-up.

中文

设置顶部新增中英文 打开 config.json ↗ 入口,通过系统为 JSON 文件关联的 IDE/文本编辑器,打开当前独立 daemon 实际使用的配置,包括自定义数据目录。不创建或覆盖文件;手动保存后需重启 Qwen Live 生效。旧版或内置 daemon 连接会禁用入口,不猜测另一份配置路径。

renderer 只能调用无参数动作,经过认证的 daemon 配置路径留在 Host 主进程。动作检查当前 renderer/连接/退出状态和常规文件目标;原生打开失败会显示提示,不暴露私有文件系统细节。恢复提示已核实为独立命令 qwen-live init

维护者合入 main 的 58686a66b4 已先快进同步,148 个上游改动路径与本次功能没有重叠,本地功能 diff 前后逐字节一致。集成后共 116 项针对性测试通过(Host 79、Qwen Live 37);这些是与上一份报告重叠的复验套件,不能与原总数相加。仓库构建/类型检查/CLI 打包、Host 构建/类型检查、范围内 lint/格式、双锁文件完整性、桌面依赖隔离及空白检查通过。

基线测试从缺少按钮变为一次点击调用一次 opener;独立 AST 检查确认 preload 不传路径,原生 IPC 测试使用真实临时文件与模拟 OS opener。浏览器使用实际构建产物和合成状态,验证中英文/深浅主题、成功/等待/失败/重试/不支持状态、末尾语言/主题顺序、常显滚动条和 Esc 关闭;从左向右的指针拖动后,面板和按钮仍在模拟桌面范围内且无横向溢出。点击打开按钮没有触发拖动。

未打开用户真实配置、音视频、模型服务或系统编辑器;未实测原生窗口管理器/编辑器启动、Windows/Linux、干净依赖安装及全仓测试。复用了现有依赖;上游已将根 Playwright 锁定为 1.61.1,本地根副本仍为 1.58.2,本次针对性测试与 CUA 不调用它。本功能没有引入依赖或锁文件变更。

Existing CI packaging failure / 既有 CI 打包问题

The new commit's Host CI run reached unsigned packaging after typecheck, tests and build, then failed with Packaged app declares unused permission NSCameraUsageDescription. The preceding run on 58686a66 failed with the identical message before this feature. This follow-up changes neither the packaging workflow nor the camera permission/entitlements. The existing camera-enabled Host and the old unused-permission packaging assertion still need reconciliation; this is not an all-green CI claim.

新提交的 Host CI 在类型检查、测试、构建之后进入未签名打包,因 NSCameraUsageDescription 被旧规则判定为“未使用权限”而失败。功能提交前的 58686a66 CI 已有完全相同的错误。本次未修改打包流程、相机权限或 entitlements;已有摄像头功能与旧权限检查规则仍需同步,不能把当前 CI 表述为全部通过。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed.

Not reviewed: territory coverage: 129 of 170 diff chunks were never read by a territory agent — the roster this 63,612-line diff prices is 208 agents / 326 KB of prompts, which one orchestrator context cannot carry. Reviewed: chunks 138, 139, 142, 143, 147, 167, 168, 169, plus 33 further chunks reached by the invariant and verifier diff reads (41 of 170 total).

Not reviewed: issue-fidelity (Agent 0) — not launched; the PR closes no issue, and its narrated motivating incidents were checked by the invariant and territory agents instead.

Not reviewed: removed-behavior audit (Agent 1b, whole-diff) — not launched; only the eight reviewed chunks audited their own deleted lines, so a removed export whose replacement lives in another chunk was never traced.

Not reviewed: cross-file tracer (Agent 1c) — not launched; callers of changed exports outside the reviewed territory were not swept.

Not reviewed: test coverage matrix (whole-diff) — not launched.

Not reviewed: counter-frame audit (Agent 6d) and prose-execution audit — required by the repository context manifest for this diff, never launched.

Not reviewed: whole-file invariant review of 5 heavy files not launched — live-host/src/preload/audio-engine.ts, live-host/src/renderer/main.ts, live-host/src/renderer/style.css, qwen-live/src/config.ts, qwen-live/src/daemon.ts.

Not reviewed: packages/qwen-live/src/memory/** — the local multi-library Memory subsystem (about 25 source chunks: on-disk store, retrieval, consolidation, observer, BOM-encoded config save) was not reviewed.

Not reviewed: packages/live-host/src/renderer/** and preload/** — the orb, settings, memory and subagents panels and the audio/camera engines were not reviewed except where an invariant agent read them whole.

Not reviewed: build-and-test — the packages/live-host suite (34 changed test files; separate toolchain and lockfile, needs macOS) did not run in this environment, and Integration Tests (CLI, No Sandbox) was skipped in CI with its CLI/interactive suites not run locally either.

Not reviewed: test-efficacy probe — harnessValidated null and all 42 probes inconclusive (the repo's vitest globalSetup build guard made the probe runner produce no parseable JSON), so no mutation-survivor or hunk-necessity evidence exists in either direction; 484 mutants and 446 hunks were skipped for cap.

Not reviewed: docs, test-only and generated chunks (66 of 170) — not reviewed.

Not reviewed: 162 of the diff's 170 sections, the linked-issue fidelity pass, the whole-diff test-coverage check, the removed-behavior audit, the cross-file consistency pass, the invariant check (state, timers, collections) on packages/live-host/src/preload/audio-engine.ts, the invariant check (counters, return values, error taxonomies) on packages/live-host/src/preload/audio-engine.ts, the invariant check (config fields, early returns) on packages/live-host/src/preload/audio-engine.ts, the invariant check (state, timers, collections) on packages/live-host/src/renderer/main.ts, the invariant check (counters, return values, error taxonomies) on packages/live-host/src/renderer/main.ts, the invariant check (config fields, early returns) on packages/live-host/src/renderer/main.ts, the invariant check (state, timers, collections) on packages/live-host/src/renderer/style.css, the invariant check (counters, return values, error taxonomies) on packages/live-host/src/renderer/style.css, the invariant check (config fields, early returns) on packages/live-host/src/renderer/style.css, the invariant check (state, timers, collections) on packages/qwen-live/src/config.ts, the invariant check (counters, return values, error taxonomies) on packages/qwen-live/src/config.ts, the invariant check (config fields, early returns) on packages/qwen-live/src/config.ts, the invariant check (state, timers, collections) on packages/qwen-live/src/daemon.ts, the invariant check (counters, return values, error taxonomies) on packages/qwen-live/src/daemon.ts, the invariant check (config fields, early returns) on packages/qwen-live/src/daemon.ts — its prompt was built, but no agent on record was launched with it.

Not reviewed: the counter-frame audit, the prose-execution audit — no record shows its brief reaching an agent, so this dimension was reviewed, if at all, from a prompt the run wrote for itself.

Not reviewed: reverse audit — its prompt was built, but no agent was launched with it — the pass that hunts what the rest of the review missed ran, if at all, without the method its brief carries, and cannot be certified.

Test Plan (not a blocker): 834 passed — this review observed 29247, 838 passed; 258 passed — this review observed 29247, 838 passed; 185 passed — this review observed 29247, 838 passed; 17 passed — this review observed 29247, 838 passed.

中文说明

仅完成部分审查,审查缺口已披露。

未审查(原文为英文):territory coverage: 129 of 170 diff chunks were never read by a territory agent — the roster this 63,612-line diff prices is 208 agents / 326 KB of prompts, which one orchestrator context cannot carry. Reviewed: chunks 138, 139, 142, 143, 147, 167, 168, 169, plus 33 further chunks reached by the invariant and verifier diff reads (41 of 170 total).

未审查(原文为英文):issue-fidelity (Agent 0) — not launched; the PR closes no issue, and its narrated motivating incidents were checked by the invariant and territory agents instead.

未审查(原文为英文):removed-behavior audit (Agent 1b, whole-diff) — not launched; only the eight reviewed chunks audited their own deleted lines, so a removed export whose replacement lives in another chunk was never traced.

未审查(原文为英文):cross-file tracer (Agent 1c) — not launched; callers of changed exports outside the reviewed territory were not swept.

未审查(原文为英文):test coverage matrix (whole-diff) — not launched.

未审查(原文为英文):counter-frame audit (Agent 6d) and prose-execution audit — required by the repository context manifest for this diff, never launched.

未审查(原文为英文):whole-file invariant review of 5 heavy files not launched — live-host/src/preload/audio-engine.ts, live-host/src/renderer/main.ts, live-host/src/renderer/style.css, qwen-live/src/config.ts, qwen-live/src/daemon.ts.

未审查(原文为英文):packages/qwen-live/src/memory/** — the local multi-library Memory subsystem (about 25 source chunks: on-disk store, retrieval, consolidation, observer, BOM-encoded config save) was not reviewed.

未审查(原文为英文):packages/live-host/src/renderer/** and preload/** — the orb, settings, memory and subagents panels and the audio/camera engines were not reviewed except where an invariant agent read them whole.

未审查(原文为英文):build-and-test — the packages/live-host suite (34 changed test files; separate toolchain and lockfile, needs macOS) did not run in this environment, and Integration Tests (CLI, No Sandbox) was skipped in CI with its CLI/interactive suites not run locally either.

未审查(原文为英文):test-efficacy probe — harnessValidated null and all 42 probes inconclusive (the repo's vitest globalSetup build guard made the probe runner produce no parseable JSON), so no mutation-survivor or hunk-necessity evidence exists in either direction; 484 mutants and 446 hunks were skipped for cap.

未审查(原文为英文):docs, test-only and generated chunks (66 of 170) — not reviewed.

未审查:diff 170 个片段中的 162 个、关联 issue 一致性检查、全 diff 测试覆盖检查、删除行为审计、跨文件一致性检查、不变量检查(状态、定时器、集合)(packages/live-host/src/preload/audio-engine.ts)、不变量检查(计数器、返回值、错误分类)(packages/live-host/src/preload/audio-engine.ts)、不变量检查(配置字段、提前返回)(packages/live-host/src/preload/audio-engine.ts)、不变量检查(状态、定时器、集合)(packages/live-host/src/renderer/main.ts)、不变量检查(计数器、返回值、错误分类)(packages/live-host/src/renderer/main.ts)、不变量检查(配置字段、提前返回)(packages/live-host/src/renderer/main.ts)、不变量检查(状态、定时器、集合)(packages/live-host/src/renderer/style.css)、不变量检查(计数器、返回值、错误分类)(packages/live-host/src/renderer/style.css)、不变量检查(配置字段、提前返回)(packages/live-host/src/renderer/style.css)、不变量检查(状态、定时器、集合)(packages/qwen-live/src/config.ts)、不变量检查(计数器、返回值、错误分类)(packages/qwen-live/src/config.ts)、不变量检查(配置字段、提前返回)(packages/qwen-live/src/config.ts)、不变量检查(状态、定时器、集合)(packages/qwen-live/src/daemon.ts)、不变量检查(计数器、返回值、错误分类)(packages/qwen-live/src/daemon.ts)、不变量检查(配置字段、提前返回)(packages/qwen-live/src/daemon.ts)——它的 prompt 已构建,但没有任何 agent 有记录用它启动过。

未审查:反框架审计、提示词执行审计——没有记录表明它的 brief 到达过任何 agent,这个维度即便被审查过,也是基于这次 run 自行编写的 prompt。

未审查:反向审计——它的 prompt 已构建,但没有 agent 用它启动——负责搜寻评审其余部分遗漏问题的这道工序,即便运行过,也缺失了 brief 承载的方法,无法作证。

Test Plan(非阻断):834 passed — this review observed 29247, 838 passed; 258 passed — this review observed 29247, 838 passed; 185 passed — this review observed 29247, 838 passed; 17 passed — this review observed 29247, 838 passed

— qwen3.8-max via Qwen Code /review (v0.23.0)

!isBoundedString(value['instanceNonce']) ||
!isObject(permissions) ||
!isPermissionState(permissions['microphone']) ||
(!isPermissionState(cameraPermission) && !legacyHelloWithoutCamera) ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] R1-1: The v9 hello gate rejects any current-protocol host.hello that omits permissions.camera, but the two existing Host-hello fakes in packages/cli were not updated — so npm test --workspace=packages/cli is red on this head with 11 net-new failures. Source: [test]

Both fakes (src/serve/routes/live.test.ts:62-66, src/serve/server.test.ts:41808-41812) send protocolVersion: LIVE_HOST_PROTOCOL_VERSION (now 9) with permissions: { microphone, accessibility, screenRecording } and no camera, so legacyHelloWithoutCamera is false, parseHello returns undefined, the socket closes 1002 and the coordinator stays at blocker: 'host_missing'. Measured: routes/live.test.ts 4 failures (GET /live/status returns 'host_missing' instead of 'appshot'; POST /live/start 503 instead of 200; POST /live/shortcut 409 instead of 200, and 409 instead of 500 for the persist-failure case) and server.test.ts 7 failures. Neither file is in this diff, so a path-based judgment would have waved them through as pre-existing.

Witness — test-delta against a built merge base, not a path guess:

netNew = [src/serve/routes/live.test.ts, src/serve/server.test.ts]
base 70cf36339: 14 failures (settings 13 + AppContainer 1), neither live file
PR head:        25 failures = the same 14 + 11 new
npm test --workspace="packages/cli" -> exit 1

Deterministic, so CI's VITEST_RETRY: 2 will not absorb it; GitHub already reports Test (ubuntu-latest, Node 22.x) as failing.

Suggested fix: add camera: 'granted' to the permissions object in both fakes, matching readyHello in live-host-coordinator.test.ts:94. Do not relax parseHello.

The fix must not violate live-host-coordinator.test.ts:576it('rejects a v9 Host hello that omits camera readiness', …) asserts closeCode === 1002 and blocker: 'host_missing', and :558 asserts a protocolVersion: 7 hello without camera closes 4006 with blocker: 'host_version'. The missing-camera rejection at protocol 9 is intended behaviour, so the fakes change, not the gate.

Please pin the fix with a mutation: after updating the fakes, remove camera: 'granted' again and confirm those 11 tests go red — that is the acceptance criterion for this finding.

中文说明

v9 的 hello 校验会拒绝任何缺少 permissions.camera 的当前协议 host.hello,但 packages/cli 中已有的两个 Host-hello 测试替身没有同步更新,因此该提交上 npm test --workspace=packages/cli 是红的,新增 11 个失败。

两个替身(src/serve/routes/live.test.ts:62-66src/serve/server.test.ts:41808-41812)发送 protocolVersion: LIVE_HOST_PROTOCOL_VERSION(现为 9),permissions 只有 microphone, accessibility, screenRecording,没有 camera,于是 legacyHelloWithoutCamerafalseparseHello 返回 undefined,socket 以 1002 关闭,协调器停留在 blocker: 'host_missing'。实测:routes/live.test.ts 4 个失败(GET /live/status 返回 'host_missing' 而非 'appshot'POST /live/start 返回 503 而非 200;POST /live/shortcut 返回 409 而非 200,持久化失败场景返回 409 而非 500),server.test.ts 7 个失败。这两个文件都不在本次 diff 中,因此仅按路径判断会误认为它们是既有失败。

证据来自对已构建 merge base 的 test-delta 测量:base 70cf36339 在该 workspace 失败 14 个(settings 13 + AppContainer 1),不含这两个 live 文件;PR head 失败 25 个,即同样的 14 个加 11 个新增。失败是确定性的,CI 的 VITEST_RETRY: 2 无法吸收;GitHub 上 Test (ubuntu-latest, Node 22.x) 已经显示失败。

建议修复: 在两个替身的 permissions 中补上 camera: 'granted',与 live-host-coordinator.test.ts:94readyHello 保持一致。不要放宽 parseHello

修复不得违反 live-host-coordinator.test.ts:576(断言缺少 camera 的 v9 hello 以 1002 关闭且 blocker: 'host_missing')与 :558(断言 protocolVersion: 7 且无 camera 的 hello 以 4006 关闭、blocker: 'host_version')。协议 9 下缺少 camera 被拒绝是预期行为,因此应改替身而不是改校验。

请用变异验证该修复:更新替身后,再次移除 camera: 'granted',确认这 11 个测试变红。

— qwen3.8-max via Qwen Code /review (v0.23.0)

<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] R1-31: [fails-closed] [new-surface] Adding com.apple.security.device.camera as a fourth macOS entitlement breaks the repo's own pinned least-privilege guard, and Live Host (macos-latest) is red because of it. Source: [review]

.github/workflows/live-host.yml — which this PR does not touch — asserts the opposite on two counts. assert_live_host_entitlements (:113-140) builds expected = [allow-jit, allow-unsigned-executable-memory, device.audio-input] and fails on actual.length !== expected.length || actual.some((key, index) => key !== expected[index]), then runs against both build/entitlements.mac.plist (:140) and the packaged entitlements (:144) — so a fourth key fails on length alone. Separately, :105-107 iterates for unused_permission in NSBluetoothAlwaysUsageDescription NSBluetoothPeripheralUsageDescription NSCameraUsageDescription and errors Packaged app declares unused permission $unused_permission, while camera capture needs that Info.plist key. This is the blocker the triage gate raised on this PR; re-checked against the code at 3f0a30c2, it still stands.

Witness, at the reviewed commit:

entitlements.mac.plist -> 4 keys: cs.allow-jit, cs.allow-unsigned-executable-memory,
                                  device.audio-input, device.camera
live-host.yml:120-123  -> const expected = [ ...3 keys... ]
live-host.yml:127-128  -> actual.length !== expected.length || actual.some(...)
gh pr checks 11369     -> Live Host (macos-latest)  fail  58s

Suggested fix: settle the permission surface with a maintainer, then move the guard in the same change — add com.apple.security.device.camera to the expected array at its matching index, drop NSCameraUsageDescription from the unused-permission list at :105, and add a positive assertion that the packaged Info.plist declares it with a real usage string. If camera capture is not meant to ship in the packaged desktop app, gate it out of that build instead of widening the entitlement set. Widening a sandboxed app's macOS permission surface is a product/security decision, not something a review can settle from the diff.

The assertion at :127-129 is ordered and exact-length, so a new key must be inserted at the matching index rather than appended anywhere.

Please pin the fix with a mutation: after Live Host (macos-latest) goes green, add an unrelated fifth entitlement and confirm assert_live_host_entitlements still fails.

中文说明

新增 com.apple.security.device.camera 作为第四项 macOS 权限,破坏了仓库自身固定的最小权限守卫,Live Host (macos-latest) 因此变红。

本 PR 没有改动的 .github/workflows/live-host.yml 在两处断言了相反的结论。assert_live_host_entitlements:113-140)构造 expected = [allow-jit, allow-unsigned-executable-memory, device.audio-input],并以 actual.length !== expected.length || actual.some((key, index) => key !== expected[index]) 判定失败,随后分别对 build/entitlements.mac.plist:140)和打包产物中的权限文件(:144)执行——因此多出第四项仅凭数量就会失败。另外 :105-107 会遍历 for unused_permission in NSBluetoothAlwaysUsageDescription NSBluetoothPeripheralUsageDescription NSCameraUsageDescription,并报 Packaged app declares unused permission $unused_permission,而摄像头能力恰好需要这个 Info.plist 键。这正是准入门禁在本 PR 上提出的阻塞项;对照 3f0a30c2 的代码复核后,它依然成立。

建议修复: 与维护者确认权限面,然后在同一次改动中同步守卫——把 com.apple.security.device.camera 按对应下标加入 expected 数组,从 :105 的未使用权限列表中移除 NSCameraUsageDescription,并补充一条正向断言,确认打包后的 Info.plist 声明了它且带有真实用途说明。如果摄像头能力本就不应进入打包后的桌面应用,则应把它从该构建中排除,而不是放宽权限集合。放宽一个沙箱化 macOS 应用的权限面属于产品/安全决策,无法仅凭 diff 判定。

:127-129 的断言是有序且严格等长的,因此新增键必须插入到对应下标,而不能随意追加。

请用变异验证该修复:在 Live Host (macos-latest) 变绿后,再加入一项无关的第五个权限,确认 assert_live_host_entitlements 仍然失败。

— qwen3.8-max via Qwen Code /review (v0.23.0)

this.cancelReconnect();
this.clearHeartbeatTimer();
this.intentionalClose = true;
this.quitPromise = this.quitConnection(socket, target).then(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] R1-2: [certifies-falsely] [new-surface] requestQuit() latches intentionalClose = true and stops discovery on entry, but the rejection handler restores only quitPromise — so a failed quit on the shared-daemon socket path leaves the Host permanently unable to report a disconnect or reconnect, while still publishing a ready snapshot. Source: [probe]

On the shared-daemon path welcome.daemonShutdownV1 is absent, so shutdownTarget is undefined and quitConnection takes the socket.send(host.action stop) branch. When that fails — buffer saturated so canSendHostControlMessage throws (:322-330), the socket tearing down before the send callback, or the 75 s QUIT_TIMEOUT_MS — the handler clears quitPromise and rethrows, but intentionalClose stays true and discovery stays stopped. socket.on('error') (:885, || this.intentionalClose) and socket.on('close') (:897, if (this.intentionalClose) return;) then both short-circuit, so when the daemon actually goes away the Host publishes no disconnected snapshot and never calls scheduleReconnect(). daemon.start() runs exactly once at boot (index.ts:2456), and only connect() (:621) writes intentionalClose = false — unreachable, because quitState = 'failed' latches off activateNativeServices (:1199) and failRequiredDaemonMessage (:976), the one path that calls forceReconnectNow(). The overlay keeps rendering a live-looking call while every subsequent utterance is dropped.

Witness, intact PR vs the flip:

INTACT: intentionalClose after failure = true / socket still held = true
        snapshots published after the daemon died = []
        getSnapshot() still reports = {"phase":"ready",..."state":"listening"}
        handshakes 1 -> 1 after a new discovery record
FLIP  : intentionalClose after failure = false
        snapshots published after the daemon died =
          [{"phase":"disconnected","error":"daemon_disconnected"},{"phase":"connecting"},...]

The flip also shows the fix needs both halves — with intentionalClose alone repaired, handshakes stayed at 1 because discovery.stop() is never undone, so scheduleReconnect only redials the stale currentRecord.

Suggested fix: in the rejection handler, restore the pre-quit state scoped so the authenticated-target latch is untouched — if (!this.quitTarget) { this.intentionalClose = false; this.discovery.start(); if (this.socket) this.armHeartbeat(this.heartbeatIntervalMs); }. If restoring the live session is not wanted, the minimum is to publish a terminal snapshot so the UI stops claiming ready.

The restore must stay inside if (!this.quitTarget): quit-connection.test.tsit('retries the same authenticated target after cleanup failure and disconnect', …) asserts value.handshakes() === 1 and that every retry carries the original x-qwen-live-nonce, so clearing the latch on the quitTarget path would open a second WebSocket to a different instance and break it.

Please pin the fix with a mutation: add a case to quit-connection.test.ts using fixture({ standalone: false }) that fails the stop frame, then after assert.rejects(requestQuit()) closes the peer and asserts value.snapshots gains a 'disconnected' entry and value.handshakes() === 2; removing the restore must turn it red.

中文说明

requestQuit() 在入口处就把 intentionalClose = true 并停止 discovery,但拒绝处理分支只恢复了 quitPromise——因此在共享 daemon 的 socket 路径上,一次失败的退出会让 Host 永久无法上报断连或重连,同时仍然发布 ready 快照。

共享 daemon 路径下 welcome.daemonShutdownV1 不存在,shutdownTarget 为 undefined,quitConnectionsocket.send(host.action stop) 分支。当它失败时(缓冲区打满导致 canSendHostControlMessage 抛错(:322-330)、socket 在发送回调前断开、或 75 秒 QUIT_TIMEOUT_MS 超时),处理分支只清除 quitPromise 并重新抛出,而 intentionalClose 仍为 true、discovery 仍处于停止状态。于是 socket.on('error'):885|| this.intentionalClose)与 socket.on('close'):897if (this.intentionalClose) return;)都会短路:daemon 真的消失时,Host 不会发布 disconnected 快照,也永远不会调用 scheduleReconnect()daemon.start() 在启动时只执行一次(index.ts:2456),而只有 connect():621)会写入 intentionalClose = false——这条路径不可达,因为 quitState = 'failed' 会永久关闭 activateNativeServices:1199)和 failRequiredDaemonMessage:976),而后者正是唯一调用 forceReconnectNow() 的路径。界面会继续显示一个看起来仍在进行的通话,而之后每一次用户发言都被丢弃。

实测证据(原始 PR 与变异对照):原始代码下 intentionalClose after failure = truesocket still held = truesnapshots published after the daemon died = []getSnapshot() 仍报告 {"phase":"ready",..."state":"listening"}、新 discovery 记录之后 handshakes 1 -> 1;加入恢复语句后 intentionalClose after failure = false,并发布了 disconnected / connecting 快照。变异同时说明修复需要两半:只修 intentionalClosehandshakes 仍为 1,因为 discovery.stop() 从未被撤销,scheduleReconnect 只会重拨过期的 currentRecord

建议修复: 在拒绝处理分支中恢复退出前状态,并限定作用域以免影响已认证目标的锁定——if (!this.quitTarget) { this.intentionalClose = false; this.discovery.start(); if (this.socket) this.armHeartbeat(this.heartbeatIntervalMs); }。如果不希望恢复通话,最低限度也应发布一个终态快照,让界面不再声称 ready

恢复逻辑必须留在 if (!this.quitTarget) 内:quit-connection.test.ts'retries the same authenticated target after cleanup failure and disconnect' 断言 value.handshakes() === 1 且每次重试都携带原始 x-qwen-live-nonce,因此在 quitTarget 路径上清除该锁会向另一个实例打开第二条 WebSocket 并使该测试失败。

请用变异验证该修复:在 quit-connection.test.ts 中用 fixture({ standalone: false }) 增加一个用例,让 stop 帧发送失败,随后在 assert.rejects(requestQuit()) 之后关闭对端,断言 value.snapshots 出现 'disconnected'value.handshakes() === 2;移除恢复语句后该断言必须变红。

— qwen3.8-max via Qwen Code /review (v0.23.0)

() => this.closeSocket(1000, 'host quitting'),
() => {
this.quitPromise = undefined;
throw new Error(liveMessage('host.error.quitUnconfirmed'));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] R1-3: [fails-closed] [new-surface] Every /live/quit failure — including "the daemon is no longer there to answer" — collapses into one unconfirmed-quit rejection, and quitTarget is never cleared, so the Host app can never be quit and never reconnects again. Source: [probe]

quitConnection's fetch (:290-297) has no try/catch and never inspects error.cause.code, and the rejection handler takes no parameter, so the cause is discarded and only quitPromise is cleared. quitTarget has exactly two occurrences in the file — the declaration at :224 and the write at :265 — and nothing ever clears it, so every later requestQuit() re-selects the same dead target and fails identically. After one failed Quit against a daemon that has since exited, quitHost() never reaches quitApproved = true; app.quit() (index.ts:690-691), before-quit preventDefault()s without quitApproved (:2245-2250), the tray Quit (:2211) and the IPC route (:1504) both re-enter quitHost, daemon.stop() (:2255) sits after the quitApproved gate, and activateNativeServices (:1199) refuses forever once quitState !== undefined. The Electron app becomes a zombie with no UI path to exit short of kill -9. The diagnostic cannot distinguish causes either: index.ts:684 logs only error.name.

Witness, intact PR vs the flip:

INTACT: attempt 1 rejected key = ..."host.error.quitUnconfirmed"... error.cause = undefined
        quitTarget latched = {"url":"http://127.0.0.1:38925",..."instanceNonce":"authenticated_instance_01"}
        attempt 2 (daemon gone) outcome = rejected / error.name = Error / error.cause = undefined
        /live/quit attempts = 1   <-- attempt 2 never reached a server
        attempt 3 outcome = rejected
FLIP  : quitTarget latched = undefined / attempt 2 = resolved / attempt 3 = resolved

Suggested fix: wrap the fetch and classify the failure. When no HTTP response was ever received and the cause is connection-level ((error.cause as NodeJS.ErrnoException)?.code in ECONNREFUSED / ECONNRESET / ENOTFOUND, or a 404/410 from /live/quit), the daemon the user asked to stop is already gone — clear this.quitTarget and resolve, or reject with a distinct host.error.quitDaemonGone that quitHost treats as exit-approved. Keep the sticky target and the quitUnconfirmed rejection for every case where a response was received but did not confirm. Carry the cause into the diagnostic instead of error.name.

The classification must key on "no response received", not on "requestQuit failed": quit-connection.test.ts:214-247 requires that a received 500 response keeps quitTarget sticky — assert.equal(attempts, 2), both requests carrying the original x-qwen-live-nonce, and assert.equal(value.handshakes(), 1) after reconnectNow()/forceReconnectNow().

Please pin the fix with a mutation: welcome with daemonShutdownV1, close the fixture HTTP server so the recorded port refuses, call requestQuit() (asserts it rejects), then call it again and assert it resolves with requests.length === 0; removing the classification makes the second call take the HTTP path again and reject.

中文说明

所有 /live/quit 失败——包括"daemon 已经不在、无法应答"——都被折叠成同一个"退出未确认"的拒绝,且 quitTarget 从不清除,于是 Host 应用既永远无法退出,也永远不会再重连。

quitConnectionfetch:290-297)没有 try/catch,也从不检查 error.cause.code;拒绝处理分支不接收参数,因此原因被丢弃,只清除了 quitPromisequitTarget 在文件中只出现两次——:224 的声明和 :265 的写入——没有任何地方清除它,所以之后每次 requestQuit() 都会重新选中同一个已失效的目标并以同样方式失败。在一次针对已退出 daemon 的退出失败之后:quitHost() 永远到不了 quitApproved = true; app.quit()index.ts:690-691);before-quit 在没有 quitApproved 时会 preventDefault():2245-2250);托盘退出(:2211)与 IPC 路径(:1504)都会重新进入 quitHostdaemon.stop():2255)位于 quitApproved 判断之后;而 quitState !== undefined 之后 activateNativeServices:1199)会永久拒绝。Electron 应用变成僵尸进程,除了 kill -9 之外没有任何界面路径可以退出。诊断信息也无法区分原因:index.ts:684 只记录 error.name

实测证据:原始代码下第一次尝试被拒(error.cause = undefined)、quitTarget 被锁定、第二次尝试(daemon 已消失)仍被拒且 /live/quit attempts = 1(第二次根本没到达服务器)、第三次同样被拒;清除 quitTarget 的变异下第二、三次尝试均 resolve。

建议修复: 包裹 fetch 并对失败分类。当从未收到 HTTP 响应且原因是连接级错误((error.cause as NodeJS.ErrnoException)?.code 属于 ECONNREFUSED / ECONNRESET / ENOTFOUND,或 /live/quit 返回 404/410)时,用户想停止的 daemon 已经不存在——清除 this.quitTarget 并 resolve,或以一个独立的 host.error.quitDaemonGone 拒绝、由 quitHost 视为已批准退出。对于收到了响应但未确认的情形(非 2xx、nonce 不匹配、重定向、超时),保留粘性目标和 quitUnconfirmed 拒绝。同时把真实原因带入诊断,而不是只记 error.name

分类必须以"是否收到响应"为依据,而不是"requestQuit 是否失败":quit-connection.test.ts:214-247 要求收到 500 响应quitTarget 保持粘性——assert.equal(attempts, 2)、两次请求都携带原始 x-qwen-live-nonce,且 reconnectNow()/forceReconnectNow() 之后 assert.equal(value.handshakes(), 1)

请用变异验证该修复:以 daemonShutdownV1 完成 welcome,关闭测试用 HTTP 服务使记录的端口拒绝连接,调用 requestQuit()(断言被拒),再次调用并断言其 resolve 且 requests.length === 0;移除分类逻辑后第二次调用会重新走 HTTP 路径并被拒。

— qwen3.8-max via Qwen Code /review (v0.23.0)

? this.socket
: undefined;
const target =
this.quitTarget ?? (socket ? this.shutdownTarget : undefined);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] R1-4: [certifies-falsely] [new-surface] requestQuit() reports success without ever attempting the shutdown, because the HTTP-only /live/quit path is gated on a WebSocket being OPEN while shutdownTarget is wiped by every disconnect — orphaning the standalone daemon. Source: [probe]

quitConnection's target branch (:284-311) never touches socket; it needs only target.url, target.token and target.instanceNonce. Yet :264 considers shutdownTarget only when socket is truthy, and socket is non-undefined only when welcomed && snapshot.phase === 'ready' && readyState === OPEN (:257-262). shutdownTarget is cleared at :600 (connect), :893 (socket.on('close')), :1030 (closeSocket) and :1053 (terminateSocket), so inside any reconnect window target is undefined, quitConnection(undefined, undefined) falls through to if (!socket) return; (:319) and the promise resolvesindex.ts:690-691 sets quitApproved = true and calls app.quit(). Windows that all clear shutdownTarget before the next welcome: reconnectNow() (:347-353, fired by index.ts:701 2.5 s after every readiness change), forceReconnectNow() (:356-362, reachable from an audio-transport failure while the user stares at an error overlay and clicks Quit), the armHeartbeat 4008 close (:1083, 15 s at the default interval), and scheduleReconnect after the daemon's own 4008. Qwen Live then appears to quit while the qwen-live daemon keeps running indefinitely — packages/qwen-live/src/index.ts:113-124 exits only on SIGINT/SIGTERM or a confirmed /live/quit — holding the port, the discovery file, the session log, and the Proactive monitors and background agents this PR adds, with no UI attached. This contradicts the suite's own stated intent, "does not report a timed-out shutdown as success": here success is reported with no attempt at all.

Witness — the deciding line is the request count:

INTACT: shutdownTarget while ready = { url:'http://127.0.0.1:35153', ..., token:'quit-token' }
        after forceReconnectNow: shutdownTarget = undefined  socket.readyState = 0 (CONNECTING)
        requestQuit outcome = resolved   /live/quit requests = 0
FLIP  : after forceReconnectNow: shutdownTarget = { ... }
        requestQuit outcome = resolved   /live/quit requests = 1

Suggested fix: key the authenticated shutdown target to the instance it was verified against rather than to the live socket. Store shutdownTarget at welcome together with record.instanceNonce (already nonce-verified at :684-694), stop clearing it in socket.on('close') / terminateSocket / connect, and clear it only when handleDiscovery installs a record with a different instanceNonce. Then select it as this.quitTarget ?? (this.shutdownTarget?.instanceNonce === this.currentRecord?.instanceNonce ? this.shutdownTarget : undefined), independent of socket, passing socket through only for the fallback stop action.

The retained target must still come only from a nonce-verified welcome, never from currentRecord alone: quit-connection.test.ts:137-147 ('closes only Host when there is no authenticated connection') asserts assert.equal(wrongInstance.requests.length, 0) for a welcome whose daemonInstanceNonce does not match the discovery record, and for a connection that was never started.

Please pin the fix with a mutation: welcome with daemonShutdownV1, then value.peer.close(1001, …) (or forceReconnectNow()) so the host is inside the reconnect window, then await connection.requestQuit() and assert value.requests[0].url === '/live/quit' carrying the original nonce; with the socket gate in place the request list stays empty.

中文说明

requestQuit() 会在从未尝试关闭 daemon 的情况下报告成功:仅走 HTTP 的 /live/quit 路径被"WebSocket 必须处于 OPEN"这一条件门控,而 shutdownTarget 会被每一次断连清除——结果是独立 daemon 被遗弃。

quitConnectiontarget 分支(:284-311)完全不使用 socket,只需要 target.urltarget.tokentarget.instanceNonce。但 :264 只在 socket 为真时才考虑 shutdownTarget,而 socket 仅在 welcomed && snapshot.phase === 'ready' && readyState === OPEN:257-262)时才非 undefined。shutdownTarget 会在 :600connect)、:893socket.on('close'))、:1030closeSocket)和 :1053terminateSocket)被清除,因此在任何重连窗口内 target 都是 undefined,quitConnection(undefined, undefined) 会落到 if (!socket) return;:319),promise 直接 resolve——index.ts:690-691 于是设置 quitApproved = true 并调用 app.quit()。会在下一次 welcome 之前清除 shutdownTarget 的窗口包括:reconnectNow():347-353,由 index.ts:701每次就绪状态变化后 2.5 秒触发)、forceReconnectNow():356-362,音频传输失败时可触发,此时用户正看着错误界面并点击退出)、armHeartbeat 的 4008 关闭(:1083,默认间隔下为 15 秒),以及 daemon 自身发出 4008 之后的 scheduleReconnect。于是 Qwen Live 看起来已退出,而 qwen-live daemon 会无限期继续运行——packages/qwen-live/src/index.ts:113-124 只在 SIGINT/SIGTERM 或已确认的 /live/quit 时退出——继续占用端口、discovery 文件、会话日志,以及本 PR 新增的 Proactive 监控和后台任务,且没有任何界面与之相连。这与测试套件自己声明的意图"does not report a timed-out shutdown as success"相矛盾:这里是在完全没有尝试的情况下报告成功。

实测证据(关键在于请求计数):原始代码下 forceReconnectNow 之后 shutdownTarget = undefinedsocket.readyState = 0 (CONNECTING)requestQuit outcome = resolved/live/quit requests = 0;变异后 shutdownTarget 保留,/live/quit requests = 1

建议修复: 让已认证的关闭目标绑定到它被校验时对应的实例,而不是绑定到当前 socket。在 welcome 时连同 record.instanceNonce 一起保存 shutdownTarget(该 nonce 已在 :684-694 校验),不再在 socket.on('close') / terminateSocket / connect 中清除它,只在 handleDiscovery 装入不同 instanceNonce 的记录时清除。然后以 this.quitTarget ?? (this.shutdownTarget?.instanceNonce === this.currentRecord?.instanceNonce ? this.shutdownTarget : undefined) 选择目标,与 socket 无关,socket 仅用于回退的 stop 动作。

保留的目标仍必须只来自经过 nonce 校验的 welcome,绝不能仅凭 currentRecordquit-connection.test.ts:137-147('closes only Host when there is no authenticated connection')断言当 welcome 的 daemonInstanceNonce 与 discovery 记录不匹配、以及连接从未启动时,assert.equal(wrongInstance.requests.length, 0)

请用变异验证该修复:以 daemonShutdownV1 完成 welcome,然后 value.peer.close(1001, …)(或 forceReconnectNow())使 host 处于重连窗口内,再 await connection.requestQuit() 并断言 value.requests[0].url === '/live/quit' 且携带原始 nonce;在存在 socket 门控时请求列表会是空的。

— qwen3.8-max via Qwen Code /review (v0.23.0)

duration_sec: { type: 'number', exclusiveMinimum: 0 },
reminder_text: { type: 'string', minLength: 1 },
},
minProperties: 1,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R1-23 (2 of 5 locations): the six new tools' argument names are declared twice — once in these JSON-schema properties blocks and once in the hand-listed allowlist inside parseProactiveArguments (orchestrator/live-session.ts:186-212) — with nothing tying the two copies together, and the allowlist (not additionalProperties: false, which the provider may not enforce) is the gate that actually runs. See location 1 of 5 (subagents/types.ts:101) for the pattern, the witness and the general fix. Source: [probe]

Failure scenario: 26 argument names (target_title, target_title_contains, title, modalities, condition, trigger_response, narration_focus, narration_style, repeat, duration_sec, reminder_text, …) are duplicated verbatim across two files in this same PR. The first field added to a schema here without the matching allowlist entry makes a schema-valid model call fail at runtime with `Unknown Proactive argument${…}: ${unknown.join(', ')}.` (live-session.ts:214), which surfaces to the user as a failed voice round trip; the mirror mistake (allowlist entry without a schema property) advertises nothing to the model while silently accepting the key.

Witness (sweep over the real population — schema keys imported from the real LIVE_SESSION_TOOLS/PROACTIVE_SESSION_TOOLS, allowlist literals parsed out of the real live-session.ts, both directions):

create_proactive_monitor: SCHEMA_ONLY=[] ALLOW_ONLY=[]
create_live_narration   : SCHEMA_ONLY=[] ALLOW_ONLY=[]
create_proactive_timer  : SCHEMA_ONLY=[] ALLOW_ONLY=[]
update_proactive_task   : SCHEMA_ONLY=[] ALLOW_ONLY=[]  (11 names, identical both sides)
cancel_proactive_task   : SCHEMA_ONLY=[] ALLOW_ONLY=[]
TOTAL gated schema properties swept: 26; tools with divergence: 0

0 divergence today — and grep for allowlist|Unknown Proactive argument|parseProactiveArguments across *.test.ts finds no test cross-checking schema against allowlist, so 0 tests pinning it either.

Suggested fix: derive the allowlist from the authoritative declarations instead of restating them — look the tool up in PROACTIVE_SESSION_TOOLS by function.name and build the set from Object.keys(tool.function.parameters.properties ?? {}).

The current chain ends in an empty allowlist for any tool name that is not one of the six (: [], live-session.ts:211), and LIST_PROACTIVE_TASKS_TOOL declares properties: {} (definitions.ts:441-444) — the derivation must keep rejecting all keys for the list tool and for unknown names.

Please pin the fix with a mutation: a case in orchestrator/live-session.test.ts asserting each PROACTIVE_SESSION_TOOLS member's declared property names are all accepted by the proactive dispatch path, and that a name outside the six still rejects every key; replacing the derivation with a stale hand-list must turn it red.

中文说明

[Suggestion] R1-23(5 处位置之 2):六个新工具的参数名被声明了两次——一次在这些 JSON-schema 的 properties 块中,一次在 parseProactiveArguments 内部手写的白名单里(orchestrator/live-session.ts:186-212)——两份副本之间没有任何关联,而真正起作用的门控是白名单(不是 provider 可能并不强制执行的 additionalProperties: false)。该模式、证据与总体修复见第 1 处位置(subagents/types.ts:101)。

失败场景: 26 个参数名(target_titletarget_title_containstitlemodalitiesconditiontrigger_responsenarration_focusnarration_stylerepeatduration_secreminder_text 等)在同一个 PR 的两个文件中被逐字重复。在这里给 schema 增加一个字段而没有同步白名单条目,会让一个 schema 合法的模型调用在运行时失败并返回 `Unknown Proactive argument${…}: ${unknown.join(', ')}.`live-session.ts:214),对用户表现为一次失败的语音往返;反向错误(白名单有而 schema 没有)则不会向模型广告任何东西,却会静默接受该键。

证据(对真实总体的扫描——schema 键从真实的 LIVE_SESSION_TOOLS/PROACTIVE_SESSION_TOOLS 导入,白名单字面量从真实的 live-session.ts 解析,双向比较):五个工具均为 SCHEMA_ONLY=[] ALLOW_ONLY=[]update_proactive_task 两侧 11 个名字完全一致),共扫描 26 个受门控的 schema 属性,出现分歧的工具数为 0。今天分歧为 0——而且在 *.test.ts 中 grep allowlist|Unknown Proactive argument|parseProactiveArguments 找不到任何交叉校验 schema 与白名单的测试,因此也没有测试固定它。

建议修复: 从权威声明派生白名单,而不是重述它们——按 function.namePROACTIVE_SESSION_TOOLS 中查找该工具,并用 Object.keys(tool.function.parameters.properties ?? {}) 构造集合。

当前链路对不属于这六个工具的任何名字都会以空白名单收尾(: []live-session.ts:211),而 LIST_PROACTIVE_TASKS_TOOL 声明的是 properties: {}definitions.ts:441-444)——派生实现必须继续对该 list 工具和未知名字拒绝所有键。

请用变异验证该修复:在 orchestrator/live-session.test.ts 中新增用例,断言 PROACTIVE_SESSION_TOOLS 每个成员声明的属性名都能被 proactive 派发路径接受,且六个之外的名字仍然拒绝所有键;把派生实现替换回过期的手写列表后断言必须变红。

— qwen3.8-max via Qwen Code /review (v0.23.0)

? {
audio: {
input: { format: { type: 'pcm', sample_rate: 16_000 } },
output: { format: { type: 'pcm', sample_rate: 24_000 } },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R1-23 (3 of 5 locations): the wire PCM rates are written as fresh literals instead of referencing this module's own exported QWEN_REALTIME_INPUT_SAMPLE_RATE (:27) and QWEN_REALTIME_OUTPUT_SAMPLE_RATE (:28) — which a repo-wide grep shows nothing reads. See location 1 of 5 (subagents/types.ts:101) for the pattern, the witness and the general fix. Source: [probe]

Failure scenario: the 24 kHz output contract now lives in three unlinked places — the exported constant at :28 (read by nobody), this new wire declaration at :2217, and the playback side's private const OUTPUT_SAMPLE_RATE = 24_000; in packages/live-host/src/preload/audio-engine.ts:17. The constants are the module's declared contract for the provider's PCM rate, so a maintainer who changes one (or adds a model whose output rate differs) updates the constant and the host, leaves this declaration behind, and the provider keeps sending 24 kHz while the host resamples by a different number — pitch-shifted or garbled playback with no compile error, because the pinning test hardcodes the literals too (realtime-session.test.ts:264-267 asserts sample_rate: 16000/24000).

Witness: repo-wide grep across all of packages/ finds no read sites for either exported name — only the declarations at :27-28 plus a duplicate pair at packages/cli/src/serve/live/qwen-realtime-session.ts:15-16.

Suggested fix: use sample_rate: QWEN_REALTIME_INPUT_SAMPLE_RATE / QWEN_REALTIME_OUTPUT_SAMPLE_RATE here so the exported constants become the single in-package source for what is declared on the wire, and have packages/live-host/src/preload/audio-engine.ts and proactive/scheduler.ts bind to them rather than to private copies.

realtime-session.test.ts:264-267 currently pins input: { format: { type: 'pcm', sample_rate: 16000 } }, output: { format: { type: 'pcm', sample_rate: 24000 } } — the substitution must keep those exact values on the wire.

Please pin the fix with a mutation: extend the existing model-gate test (:236-268) to assert settings['audio'].output.format.sample_rate === QWEN_REALTIME_OUTPUT_SAMPLE_RATE (imported) rather than the literal 24000; restoring the literal plus a changed constant then goes red.

中文说明

[Suggestion] R1-23(5 处位置之 3):线上的 PCM 采样率被写成新的字面量,而没有引用本模块自己导出的 QWEN_REALTIME_INPUT_SAMPLE_RATE:27)和 QWEN_REALTIME_OUTPUT_SAMPLE_RATE:28)——全仓库 grep 显示没有任何地方读取它们。该模式、证据与总体修复见第 1 处位置(subagents/types.ts:101)。

失败场景: 24 kHz 输出契约现在存在于三个互不关联的位置——:28 的导出常量(无人读取)、:2217 这个新的线上声明,以及播放侧 packages/live-host/src/preload/audio-engine.ts:17 中私有的 const OUTPUT_SAMPLE_RATE = 24_000;。这些常量是本模块对 provider PCM 采样率的声明契约,因此维护者若改动其中一处(或新增一个输出采样率不同的模型),会更新常量和 host,却漏掉这里的声明,于是 provider 继续发送 24 kHz,而 host 按另一个数值重采样——播放出现音高偏移或失真,却没有编译错误,因为固定该行为的测试也硬编码了字面量(realtime-session.test.ts:264-267 断言 sample_rate: 16000/24000)。

证据: 对整个 packages/ 的全仓库 grep 显示这两个导出名没有任何读取点——只有 :27-28 的声明,外加 packages/cli/src/serve/live/qwen-realtime-session.ts:15-16 的一对重复声明。

建议修复: 在此处使用 sample_rate: QWEN_REALTIME_INPUT_SAMPLE_RATE / QWEN_REALTIME_OUTPUT_SAMPLE_RATE,让这些导出常量成为包内关于线上声明的唯一来源;并让 packages/live-host/src/preload/audio-engine.tsproactive/scheduler.ts 绑定到它们,而不是各自的私有副本。

realtime-session.test.ts:264-267 目前固定了 input: { format: { type: 'pcm', sample_rate: 16000 } }, output: { format: { type: 'pcm', sample_rate: 24000 } }——替换后线上必须保持这些完全相同的数值。

请用变异验证该修复:扩展现有的模型门控测试(:236-268),断言 settings['audio'].output.format.sample_rate === QWEN_REALTIME_OUTPUT_SAMPLE_RATE(导入的常量)而不是字面量 24000;这样在恢复字面量并修改常量后测试会变红。

— qwen3.8-max via Qwen Code /review (v0.23.0)

Comment on lines +602 to +603
const audioSeconds =
state.audio.reduce((total, input) => total + input.bytes, 0) /

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R1-23 (4 of 5 locations): the PCM16 bytes→seconds conversion is hard-coded as 16_000 * 2 twice in this file (poll() at :602-604 for the audioSeconds diagnostic, hasWarmEvidence() at :636-638 for the gate) instead of using the exported input-rate constant from the module this file already imports QWEN_REALTIME_LIMITS from. See location 1 of 5 (subagents/types.ts:101) for the pattern, the witness and the general fix. Source: [probe]

Failure scenario: the two copies must agree with each other and with the rate the realtime session actually negotiates. QWEN_REALTIME_INPUT_SAMPLE_RATE = 16_000 is exported at realtime/realtime-session.ts:27 and the session pins input: { format: { type: 'pcm', sample_rate: 16_000 } } at :2216. If that rate is ever changed in one place, audio.minEvalDurationSec silently keeps measuring against 16 kHz: at a 24 kHz input the gate would request evaluations after only ⅔ of the configured audio duration, and the audioSeconds value logged by proactive.evaluation_gate would disagree with the number the gate actually used — the diagnostic that exists to explain a stuck task would point the wrong way. The duplication is also within a single file, so the two sites can drift independently of any rate change.

Witness: grep across all of packages/ finds no read site for QWEN_REALTIME_INPUT_SAMPLE_RATE; the two (16_000 * 2) literals are at scheduler.ts:604 and :638.

Suggested fix: import QWEN_REALTIME_INPUT_SAMPLE_RATE in the existing import from ../realtime/realtime-session.js and add one private helper used by both sites — private audioSeconds(state: MediaEvidence): number { return state.audio.reduce((total, input) => total + input.bytes, 0) / (QWEN_REALTIME_INPUT_SAMPLE_RATE * 2); }.

export const QWEN_REALTIME_INPUT_SAMPLE_RATE = 16_000; (realtime-session.ts:27) — input is 16 kHz while output is 24 kHz per QWEN_REALTIME_OUTPUT_SAMPLE_RATE on the next line, so the helper must bind to the input constant, not a shared one.

A constant substitution and de-duplication adds no guard or branch to pin (N/A) — the existing audio-window tests (scheduler.test.ts:749, :777) must stay green.

中文说明

[Suggestion] R1-23(5 处位置之 4):PCM16 字节→秒的换算在本文件中被硬编码为 16_000 * 2 两次(poll():602-604 用于 audioSeconds 诊断,hasWarmEvidence():636-638 用于门控),而没有使用本文件已经从其导入 QWEN_REALTIME_LIMITS 的那个模块所导出的输入采样率常量。该模式、证据与总体修复见第 1 处位置(subagents/types.ts:101)。

失败场景: 这两份副本必须彼此一致,也必须与 realtime 会话实际协商的采样率一致。QWEN_REALTIME_INPUT_SAMPLE_RATE = 16_000realtime/realtime-session.ts:27 导出,会话在 :2216 固定了 input: { format: { type: 'pcm', sample_rate: 16_000 } }。如果该采样率有一天只在一处被修改,audio.minEvalDurationSec 会静默地继续按 16 kHz 计算:在 24 kHz 输入下,门控会在只达到配置音频时长 ⅔ 时就请求评估,而 proactive.evaluation_gate 记录的 audioSeconds 会与门控实际使用的数值不一致——那个本用于解释任务卡住的诊断会指向错误方向。这种重复还发生在同一个文件内部,因此两处即使采样率不变也可能各自漂移。

证据: 对整个 packages/ 的 grep 找不到 QWEN_REALTIME_INPUT_SAMPLE_RATE 的读取点;两处 (16_000 * 2) 字面量分别在 scheduler.ts:604:638

建议修复: 在已有的 ../realtime/realtime-session.js 导入中引入 QWEN_REALTIME_INPUT_SAMPLE_RATE,并添加一个供两处使用的私有辅助方法——private audioSeconds(state: MediaEvidence): number { return state.audio.reduce((total, input) => total + input.bytes, 0) / (QWEN_REALTIME_INPUT_SAMPLE_RATE * 2); }

export const QWEN_REALTIME_INPUT_SAMPLE_RATE = 16_000;realtime-session.ts:27)——输入是 16 kHz,而下一行的 QWEN_REALTIME_OUTPUT_SAMPLE_RATE 表明输出是 24 kHz,因此该辅助方法必须绑定到输入常量,而不是某个共享常量。

常量替换与去重不会新增需要固定的守卫或分支(N/A)——现有的音频窗口测试(scheduler.test.ts:749:777)必须保持通过。

— qwen3.8-max via Qwen Code /review (v0.23.0)

if (terminal || closedByClient) return false;
if (
typeof update.instructions !== 'string' ||
update.instructions.length > 100_000

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R1-23 (5 of 5 locations): the 100 000-character instruction bound is a bare literal duplicated at two new sites in this file (:630 and here) and a third time in the caller, which derives a budget from it across a package boundary with no shared symbol. See location 1 of 5 (subagents/types.ts:101) for the pattern, the witness and the general fix. Source: [probe]

Failure scenario: orchestrator/live-session.ts:1019-1024 computes the memory prompt budget as 100_000 - buildLiveInstructions(...).length - MEMORY_SYSTEM_PROMPT.length - 1_000, and instructions() (:984-997) re-derives the base text at publish time from the current context.visualInput while the budget was fixed at attach time. The two numbers are coupled only by convention: if this file's bound is lowered, or the base instructions grow past the 1 000-char slack, configure() throws a RangeError out of publishMemoryInstructions (live-session.ts:1006-1012), which has no try/catch, so it escapes syncMemorySettings() into the settings route instead of degrading — and no compiler or test links the three copies.

Witness: the 100_000 literal appears at realtime-session.ts:627, :1963 and live-session.ts:1019; measured base instructions are 9,301 chars and MEMORY_SYSTEM_PROMPT 697 chars, so the slack is real but unenforced.

Suggested fix: export one MAX_REALTIME_INSTRUCTIONS_CHARS = 100_000 from this module, use it at :627 and :1963, and have attachMemory's budget subtract that imported constant instead of its own literal.

MemorySession enforces promptBlocks().length <= maxPromptChars (memory/session.ts:169) using the value attachMemory computed — so the exported constant must be the one both sides read, or the enforcement and the guard keep two different numbers.

A constant extraction adds no branch to pin (N/A); if the caller's budget is switched to the imported constant, live-session.test.ts's memory-attachment cases are the ones that would go red on a divergence.

中文说明

[Suggestion] R1-23(5 处位置之 5):100 000 字符的指令上限是一个裸字面量,在本文件的两个新增位置(:630 与此处)重复出现,并在调用方第三次出现——而调用方是跨包边界由它推导预算的,却没有共享符号。该模式、证据与总体修复见第 1 处位置(subagents/types.ts:101)。

失败场景: orchestrator/live-session.ts:1019-1024 把 memory 提示词预算计算为 100_000 - buildLiveInstructions(...).length - MEMORY_SYSTEM_PROMPT.length - 1_000,而 instructions():984-997)在发布时会根据当前context.visualInput 重新推导基础文本,尽管预算是在 attach 时固定的。这两个数值只靠约定耦合:如果本文件的上限被调低,或基础指令增长超过 1 000 字符的余量,configure() 会从 publishMemoryInstructionslive-session.ts:1006-1012)抛出 RangeError,而它没有 try/catch,于是异常会逃出 syncMemorySettings() 进入设置路由,而不是降级处理——并且没有编译器或测试把这三份副本关联起来。

证据: 100_000 字面量出现在 realtime-session.ts:627:1963live-session.ts:1019;实测基础指令为 9,301 字符、MEMORY_SYSTEM_PROMPT 为 697 字符,因此余量确实存在但没有被强制约束。

建议修复: 从本模块导出一个 MAX_REALTIME_INSTRUCTIONS_CHARS = 100_000,在 :627:1963 使用它,并让 attachMemory 的预算减去这个导入的常量而不是自己的字面量。

MemorySession 使用 attachMemory 计算出的值来强制 promptBlocks().length <= maxPromptCharsmemory/session.ts:169)——因此导出的常量必须是双方共同读取的那一个,否则强制约束与守卫会继续持有两个不同的数值。

常量提取不会新增需要固定的分支(N/A);如果把调用方的预算改为使用导入的常量,那么在发生分歧时会变红的是 live-session.test.ts 中的 memory attach 相关用例。

— qwen3.8-max via Qwen Code /review (v0.23.0)

'Modify an existing task selected by a unique title. Event ' +
'condition/response fields and live-narration focus/style fields are ' +
'distinct; the task kind cannot be converted by update. Omit the ' +
'selector only for an immediately adjacent reference to the ' +

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R1-29: update_proactive_task's description tells the model it may omit the title selector for any adjacent reference to the just-created task, but the dispatcher accepts a selector-less update only when the single argument is repeat: true — and the failure receipt handed back does not name the rule that was broken, so the retry is a guess rather than a repair. Source: [probe]

The description reads "…Omit the selector only for an immediately adjacent reference to the just-created task. Never use update for a new request.", while live-session.ts:1700-1704 throws ProactiveArgumentsError('An adjacent selector-less update may only set repeat=true.') unless Object.keys(args).length === 1 && args['repeat'] === true. Trigger: the user creates a monitor, then says "call it Kitchen timer instead". The model follows the description and calls update_proactive_task({"title":"Kitchen timer"}) — schema-valid (minProperties: 1, no required, title declared) and past the allowlist (live-session.ts:186-212) — so it reaches the guard and is rejected. The cost is larger than an extra round trip: renderFailureFact (proactive/tool-receipt.ts:426-443) maps invalid_arguments to one generic sentence and discards receipt.error, and live-session.ts:1816-1819 returns only renderProactiveToolReceipt(receipt), so the model is told "did not pass validation" with no indication that adding target_title fixes it. Because continuesResponse: true (definitions.ts:348) registers a continuation at realtime-session.ts:1888-1893, the model gets another response and re-issues a guess — a spoken stumble on a call the tool surface explicitly endorsed. The repair path does not cover this: detectProactiveRepairIntent (proactive/tool-repair.ts:29-45) tests only PROACTIVE_PROMISE / PROACTIVE_CANCEL_CLAIM against the assistant's transcript, never an argument-shape rejection.

Witness — the receipt carries the rule, the model never sees it:

PROBE receipt.committed = false
PROBE receipt.failure_code = invalid_arguments
PROBE receipt.error = "An adjacent selector-less update may only set repeat=true."
PROBE what the model is handed = "提醒任务未创建或修改,提交的信息未通过校验。"
PROBE does the rendered receipt name the rule it broke? NO
PROBE does the rendered receipt tell it to add a selector? NO

Suggested fix: state the enforced rule in the description — '…Omit the selector only to set repeat=true on the just-created task; every other change needs target_title or target_title_contains.' Separately, and more valuable, let renderFailureFact pass receipt.error through for invalid_arguments so the continuation can repair instead of guessing. Relaxing the handler instead is possible, but then the guard at live-session.ts:1701 and its test must change together.

The rule the wording must match is if (Object.keys(args).length !== 1 || args['repeat'] !== true)'An adjacent selector-less update may only set repeat=true.' (live-session.ts:1701-1703), and the sibling cancel path is stricter still — 'An adjacent selector-less cancel must have no arguments.' (:1763-1767) — so the two descriptions cannot share one blanket "omit the selector" sentence. The design doc does record the restriction itself ("For safety, an implicit update can only change repeat from false to true"), so the rule stays; only its advertisement is wrong.

Please pin the fix with a mutation: for the receipt half, a case in proactive/tool-receipt.test.ts (or orchestrator/live-session.test.ts) asserting that an invalid_arguments receipt whose error names the rule renders that rule back to the model — removing the pass-through makes it red. The description half is text only (N/A); if the author instead relaxes the handler, the witness is a case asserting a selector-less {title: …} update commits against the adjacent task.

中文说明

update_proactive_task 的描述告诉模型:对于刚刚创建的任务,任何紧邻的引用都可以省略标题选择器;但派发器只在唯一参数是 repeat: true 时才接受无选择器的更新——而且返回的失败回执并不说明违反了哪条规则,因此重试是猜测而不是修复。

描述写的是"…Omit the selector only for an immediately adjacent reference to the just-created task. Never use update for a new request.",而 live-session.ts:1700-1704 除非满足 Object.keys(args).length === 1 && args['repeat'] === true,否则抛出 ProactiveArgumentsError('An adjacent selector-less update may only set repeat=true.')。触发场景:用户创建一个监控器,然后说"改叫它 Kitchen timer"。模型按照描述调用 update_proactive_task({"title":"Kitchen timer"})——这在 schema 上合法(minProperties: 1、无 requiredtitle 已声明),也通过了白名单(live-session.ts:186-212)——于是到达守卫并被拒绝。代价比多一次往返更大:renderFailureFactproactive/tool-receipt.ts:426-443)把 invalid_arguments 映射为一句通用文案并丢弃 receipt.error,而 live-session.ts:1816-1819 只返回 renderProactiveToolReceipt(receipt),因此模型只被告知"未通过校验",完全不知道加上 target_title 就能修复。由于 continuesResponse: truedefinitions.ts:348)会在 realtime-session.ts:1888-1893 注册一次续接响应,模型会再得到一次响应并凭猜测重新发起——在一个工具表面明确认可该操作的通话中出现口吃般的往返。修复路径也覆盖不到这种情况:detectProactiveRepairIntentproactive/tool-repair.ts:29-45)只针对助手转录文本检测 PROACTIVE_PROMISE / PROACTIVE_CANCEL_CLAIM,从不检测参数形状被拒。

实测证据:回执中携带了具体规则(receipt.error = "An adjacent selector-less update may only set repeat=true."),但交给模型的是"提醒任务未创建或修改,提交的信息未通过校验。",探针明确测得"渲染后的回执是否说明违反的规则?NO"、"是否提示需要添加选择器?NO"。

建议修复: 在描述中写明实际强制的规则——'…Omit the selector only to set repeat=true on the just-created task; every other change needs target_title or target_title_contains.'。另外,更有价值的是让 renderFailureFactinvalid_arguments 透传 receipt.error,使续接响应能够修复而不是猜测。也可以选择放宽处理逻辑,但那样 live-session.ts:1701 的守卫及其测试必须一起修改。

措辞必须匹配的规则是 if (Object.keys(args).length !== 1 || args['repeat'] !== true)'An adjacent selector-less update may only set repeat=true.'live-session.ts:1701-1703),而同类的 cancel 路径更严格——'An adjacent selector-less cancel must have no arguments.':1763-1767)——因此两个描述不能共用一句笼统的"省略选择器"。设计文档确实记录了该限制本身("For safety, an implicit update can only change repeat from false to true"),所以规则保留,错的只是对它的宣传。

请用变异验证该修复:回执那一半,在 proactive/tool-receipt.test.ts(或 orchestrator/live-session.test.ts)中新增用例,断言一个 error 中写明规则的 invalid_arguments 回执会把该规则渲染回给模型——移除透传后断言变红。描述那一半只是文本(N/A);如果作者选择放宽处理逻辑,则验证方式是新增用例断言无选择器的 {title: …} 更新能对相邻任务提交成功。

— qwen3.8-max via Qwen Code /review (v0.23.0)

@wangxiongts

Copy link
Copy Markdown
Collaborator Author

PR #11369 review follow-up

Baseline: 3f0a30c2bf. All three maintainer merges are preserved. This is a review-finding follow-up, not a whole-PR certification.

Fix commit: 5136b2713fa249266215b1db6d55546cb21a8392, pushed to the existing PR branch after the user completed the required workflow authorization. The repository pre-commit format/lint hooks passed and preserved the reviewed/tested patch exactly (SHA256 74175b8e7f65c5fc544876862e0b7d4bf7ee808edc47ff596195ab8e9cfd73a3). No force push or history rewrite was used.

Main merge history (Asia/Shanghai): 58686a66b4 on Sep 8 18:20, 2dd185ce5e on Sep 8 19:01, and 3f0a30c2bf on Sep 9 01:36. Both first-parent comparisons and the incorporated main ranges show no changes to the Live implementation directories; no lost Live changes were found in those merges. The stale CLI hello/capture fixtures were integration gaps, not evidence that main reverted the Live implementation.

Per-comment decisions

Finding Status Decision
R1-1 fixed Updated both v9 hello fakes and the stale capture spy; all 11 originally failing CLI cases now pass.
R1-31 fixed CI requires Camera usage text and exactly four intended entitlements; additional/missing/disabled permissions remain rejected.
R1-2 fixed Failed Quit publishes a truthful failure and preserves retry/stopped media; no forced reconnect or UI restart.
R1-3 fixed Declined network-error-as-exit inference. Only original authenticated PID ESRCH or matching receipt permits success; 404/reset/EPERM controls stay rejected.
R1-4 fixed Retained verified shutdown target through same-instance reconnects; identity/credential changes revoke it.
R1-5 fixed Full readiness dominates visual-only recovery; successful source updates cannot cancel microphone recovery.
R1-7 fixed Both stop outcomes reset output bookkeeping so the next call receives playback acknowledgements.
R1-8 partial Fixed lost buffered permission/completion events across missing/different/rejected/throwing receipts; retained strict identity and retired already-resolved asks. Complete Qwen steer messageId lifecycle correlation remains open; no blind adoption of unknown refs.
R1-9 fixed Ignore a late false tool result only for its exact nonfatally failed response while transport remains usable; active/fatal/closed controls still fail.
R1-14 fixed Paired invalidated Proactive output clearing with injector playback clearing.
R1-15 fixed Retry deferred repair after any response authority settles, only when foreground is actually idle.
R1-11 fixed Reset recycle counters at authoritative ready, preventing a promise continuation from clearing a newer failure.
R1-6 fixed Mode send failures reject with localized feedback and diagnostics, without optimistic state mutation.
R1-19 deferred Keep post-call observation. Even typed session_not_found/404 may be recoverable; no safe permanent-error signal justifies arbitrary retirement.
R1-25 deferred All four complete source/mode strings currently match; formatter deduplication is maintenance, not a demonstrated defect.
R1-26 fixed Preserved the exact 3496-character SFT prompt and non-executable Func_call, adding content-free ignored-action diagnostics.
R1-27 deferred Current JPEG validation policies agree on the negative/boundary corpus; deduplication deferred.
R1-28 fixed Added continuous observed-time warm-up for slow captures while retaining the nominal-rate path and expiry/reset controls.
R1-22 fixed Oversized initial instructions now carry configuration classification and actionable setup feedback.
R1-24 fixed Removed the image-drop reason with no producer; no unsupported behavior is advertised.
R1-23 (1/5) deferred Existing enum/schema parity holds; broad cross-wire/schema restructuring is deferred.
R1-23 (2/5) deferred Existing enum/schema parity holds; broad cross-wire/schema restructuring is deferred.
R1-23 (3/5) fixed Reused the existing PCM rates or shared instruction-size constant without changing the wire values or limits.
R1-23 (4/5) fixed Reused the existing PCM rates or shared instruction-size constant without changing the wire values or limits.
R1-23 (5/5) fixed Reused the existing PCM rates or shared instruction-size constant without changing the wire values or limits.
R1-29 fixed Corrected adjacent update/cancel descriptions and returned only fixed owned validation facts, never raw exceptions.

逐项结论

意见 状态 处理
R1-1 已修复 补齐两个 v9 hello 替身并更新过期截图 spy,原 11 个失败用例全部转绿。
R1-31 已修复 CI 正向验证相机用途说明和精确四项权限,额外、缺失或禁用的权限仍失败。
R1-2 已修复 退出失败公布真实错误,保留重试与停止媒体,不强行恢复通话或切换实例。
R1-3 已修复 不接受网络错误等于退出的推论;仅匹配回执或原已认证 PID 的 ESRCH 证明允许成功。
R1-4 已修复 同实例重连保留已验证退出目标,身份或凭据改变时撤销。
R1-5 已修复 完整就绪恢复优先,视觉切换不再取消麦克风等恢复。
R1-7 已修复 两种停止终态都清理播放记录,下一通话回执恢复。
R1-8 部分修复 已修复各类回执下的残留事件丢失并防止授权复活;严格保留归属。Qwen steer 的完整 messageId 生命周期关联尚未展开,不猜测未知 ref 的归属。
R1-9 已修复 仅豁免同一非致命失败响应的迟到回执,活动响应及致命/关闭状态仍正确失败。
R1-14 已修复 失效 Proactive 清音频时同步清理 injector 播放状态。
R1-15 已修复 所有响应类型结束后均可重试延期修复,且必须前台真正空闲。
R1-11 已修复 在权威 ready 点重置回收状态,避免 Promise 回调覆盖后来的故障。
R1-6 已修复 模式发送失败返回本地化错误及诊断,不提前改动已确认状态。
R1-19 保留/延期 保留通话后后台观察;部分 session_not_found/404 本就可恢复,没有可靠信号支持任意终止重试。
R1-25 保留/延期 四种来源/模式的完整文本一致,格式化去重保留为维护项。
R1-26 已修复 保留完整 3496 字符 SFT prompt 和 Func_call 不执行语义,仅补不含内容的诊断。
R1-27 保留/延期 当前 JPEG 校验在边界及负例上一致,去重保留为维护项。
R1-28 已修复 为低速成功采集增加持续观测时间路径,保留正常帧率行为及证据过期/重置。
R1-22 已修复 初始指令超限现在携带配置错误分类和可操作提示。
R1-24 已修复 删除没有生产者的丢帧原因,不暗示未实现行为。
R1-23 (1/5) 保留/延期 现有枚举/schema 一致性验证通过,跨协议/schema 大重构暂不展开。
R1-23 (2/5) 保留/延期 现有枚举/schema 一致性验证通过,跨协议/schema 大重构暂不展开。
R1-23 (3/5) 已修复 复用 PCM 采样率或统一指令长度常量,不改变线上取值/上限。
R1-23 (4/5) 已修复 复用 PCM 采样率或统一指令长度常量,不改变线上取值/上限。
R1-23 (5/5) 已修复 复用 PCM 采样率或统一指令长度常量,不改变线上取值/上限。
R1-29 已修复 精确说明相邻更新/取消限制;回执仅映射固定自有验证事实,不透传异常。

Verification

  • Qwen Live: 884 passed, 2 real-backend manual tests skipped.
  • Live Host: 295 passed.
  • CLI Live: 208 passed (185 built-in Live, 9 route, 11 Appshot server, 3 lifecycle).
  • Fake-provider/ACP integration: 17 passed across 8 files.
  • Total non-overlapping tests: 1,404 passed, plus 2 skipped manual tests. Targeted reruns are not added to this count.
  • Root build/typecheck/bundle, Host build/typecheck, scoped lint/format and npm/pnpm integrity checks passed. No lockfile/dependency changes are introduced by these fixes.
  • A local arm64 Host app was packaged in /tmp/qwen-live-pr11369-package.Oo6HUT, without publishing/installing/launching it. The exact CI post-pack guards, ad-hoc signing, strict signature check and extracted entitlement assertions passed. Extra/missing/disabled entitlement controls reject.
  • Independent audit caught and then reverified the intentional-close handshake-error regression. Nonce/version rejection stays stable after close; failed shared stop retry really sends the stop and late ready is corrected on close.
  • No real camera, microphone, model service, Windows/Linux or full CLI/core suite validation is claimed. Existing node_modules were reused, not clean installed.

Audit record

The root agent read the complete product diff, changed tests, new review regressions and bilingual design/README updates in open-ended passes, then traced each new field and passing assertion against its real producer/consumer and negative controls. Two final clean passes followed the intentional-close correction. Independent review re-ran real loopback WebSocket nonce/version/shared-stop/late-ready controls and reported no unresolved new finding. Temporary reproduction stdout instrumentation was removed; the relevant facts became assertions and the eight affected tests plus package typechecking were rerun successfully. Final formatter checks include the standalone Host explicitly (it is excluded by root defaults).

Remaining task-identity boundary

R1-8 now preserves actionable session-level events and never certifies an unrelated job as completed. Full Qwen (not ACP's synchronous steer path) messageId injection/promotion correlation remains separate work; the user was asked whether to expand this round. Unknown-reference tasks may remain starting until an authoritative idle/closed reconciliation marks outcome unknown. Do not call that identity lifecycle fully fixed.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ This run could not certify that any of this diff was reviewed.

3 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • R2-1 wire-contract literal duplication (3 legs) — already reported as R1-23 location 1 (comment 3962901967, packages/qwen-live/src/subagents/types.ts:101), R1-23 location 2 / R1-10 (comment 3962901971, packages/qwen-live/src/tools/definitio…
  • R1-26 Proactive monitor prompt advertises a Func_call channel the session is not registered for — still stands, already reported (comment 3962901939, packages/qwen-live/src/proactive/monitor-protocol.ts:66)
  • R1-27 isBoundedJpegBase64 duplicated from realtime-session.ts — still stands, already reported (comment 3962901947, packages/qwen-live/src/proactive/realtime-monitor.ts:181)

Not reviewed: issue-fidelity (Agent 0) — not launched: its whole-diff block enumerates all 175 diff reads (28 KB), which one launch cannot carry, so the PR's linked-issue fidelity and root-cause ownership were never evaluated.

Not reviewed: removed-behavior audit (Agent 1b, whole-diff) — not launched, so a removed export whose replacement lives in another chunk was never traced.

Not reviewed: cross-file tracer (Agent 1c) — not launched, so callers of changed exports outside the reviewed territory were not swept.

Not reviewed: test coverage matrix (whole-diff) — not launched.

Not reviewed: counter-frame audit (Agent 6d) — not launched, so the diff was never reviewed outside the frame the PR description nominates.

Not reviewed: prose-execution audit — not launched, although this diff adds docs/design/2026-09-09-live-review-corrections.md and changes .github/workflows/live-host.yml.

Not reviewed: territory coverage: 169 of 175 diff chunks were never read by a territory agent (6 were) — the roster this 66,252-line diff prices is 215 agents / 390 KB of prompts, which one orchestrator context cannot carry; the review was scoped instead to the single fix commit (32 files, +2352/-88), the round-1 ledger, and the 5 heavy files whose invariant review round 1 also never ran.

Not reviewed: whole-file invariant review of 6 heavy files — not launched this round: live-host/src/main/daemon-connection.ts, live-host/src/main/index.ts, live-host/src/shared/protocol.ts, qwen-live/src/host/live-host-coordinator.ts, qwen-live/src/orchestrator/live-session.ts, qwen-live/src/realtime/realtime-session.ts (their fix-commit hunks were reviewed by the ledger-ruling agents, but no whole-file invariant walk ran).

Not reviewed: packages/qwen-live/src/memory/** — not reviewed; round 1 disclosed the same subsystem unreviewed, so it has now gone two rounds unread.

Not reviewed: build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI and their suites did not run locally, and the macOS packaging/codesign half of live-host.yml needs darwin plus Xcode so it was not exercised; the 8 new integration-tests/qwen-live-* files WERE run locally (8 files / 17 tests passed after npm run bundle) even though Integration Tests (CLI, No Sandbox) was skipped in CI.

Not reviewed: test-efficacy probe — harnessValidated null and all 47 probes inconclusive (the repo's vitest globalSetup build guard made the probe runner produce no parseable JSON), so no mutation-survivor or hunk-necessity evidence exists in either direction; 489 mutants and 448 hunks were skipped for cap.

Not reviewed: R2-29 (host_quit_failed.kind is a constant) — a verifier incidental that no verification round ruled on; carried terminal-only at low confidence.

Not explored to full depth (tool budget reached): "agent invariant-c (packages/live-host/src/renderer/main.ts)": none — I finished the walk within the tool budget (~12 calls)..

Not reviewed: "agent verify" — pointed at diff lines it never opened: it made tool calls, but none of them read the diff.

Not reviewed: the entire diff, the linked-issue fidelity pass, the whole-diff test-coverage check, the counter-frame audit, the removed-behavior audit, the prose-execution audit, the cross-file consistency pass, the invariant check (state, timers, collections) on packages/live-host/src/main/daemon-connection.ts, the invariant check (counters, return values, error taxonomies) on packages/live-host/src/main/daemon-connection.ts, the invariant check (config fields, early returns) on packages/live-host/src/main/daemon-connection.ts, the invariant check (state, timers, collections) on packages/live-host/src/main/index.ts, the invariant check (counters, return values, error taxonomies) on packages/live-host/src/main/index.ts, the invariant check (config fields, early returns) on packages/live-host/src/main/index.ts, the invariant check (state, timers, collections) on packages/live-host/src/shared/protocol.ts, the invariant check (counters, return values, error taxonomies) on packages/live-host/src/shared/protocol.ts, the invariant check (config fields, early returns) on packages/live-host/src/shared/protocol.ts, the invariant check (state, timers, collections) on packages/qwen-live/src/host/live-host-coordinator.ts, the invariant check (counters, return values, error taxonomies) on packages/qwen-live/src/host/live-host-coordinator.ts, the invariant check (config fields, early returns) on packages/qwen-live/src/host/live-host-coordinator.ts, the invariant check (state, timers, collections) on packages/qwen-live/src/orchestrator/live-session.ts, the invariant check (counters, return values, error taxonomies) on packages/qwen-live/src/orchestrator/live-session.ts, the invariant check (config fields, early returns) on packages/qwen-live/src/orchestrator/live-session.ts, the invariant check (state, timers, collections) on packages/qwen-live/src/realtime/realtime-session.ts, the invariant check (counters, return values, error taxonomies) on packages/qwen-live/src/realtime/realtime-session.ts, the invariant check (config fields, early returns) on packages/qwen-live/src/realtime/realtime-session.ts — its prompt was built, but no agent on record was launched with it.

Not reviewed: reverse audit — its prompt was built, but no agent was launched with it — the pass that hunts what the rest of the review missed ran, if at all, without the method its brief carries, and cannot be certified.

Not linted (tool limitation, not a blocker): .github/workflows/live-host.yml — actionlint embedded-shell source mapping is not yet supported.

Test Plan (not a blocker): 834 passed — this review observed 29259, 884 passed; 258 passed — this review observed 29259, 884 passed; 185 passed — this review observed 29259, 884 passed; 17 passed — this review observed 29259, 884 passed.

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

中文说明

⚠️ 本次运行无法证明这个 diff 的任何部分经过了审查。

本轮确认的 3 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查(原文为英文):issue-fidelity (Agent 0) — not launched: its whole-diff block enumerates all 175 diff reads (28 KB), which one launch cannot carry, so the PR's linked-issue fidelity and root-cause ownership were never evaluated.

未审查(原文为英文):removed-behavior audit (Agent 1b, whole-diff) — not launched, so a removed export whose replacement lives in another chunk was never traced.

未审查(原文为英文):cross-file tracer (Agent 1c) — not launched, so callers of changed exports outside the reviewed territory were not swept.

未审查(原文为英文):test coverage matrix (whole-diff) — not launched.

未审查(原文为英文):counter-frame audit (Agent 6d) — not launched, so the diff was never reviewed outside the frame the PR description nominates.

未审查(原文为英文):prose-execution audit — not launched, although this diff adds docs/design/2026-09-09-live-review-corrections.md and changes .github/workflows/live-host.yml.

未审查(原文为英文):territory coverage: 169 of 175 diff chunks were never read by a territory agent (6 were) — the roster this 66,252-line diff prices is 215 agents / 390 KB of prompts, which one orchestrator context cannot carry; the review was scoped instead to the single fix commit (32 files, +2352/-88), the round-1 ledger, and the 5 heavy files whose invariant review round 1 also never ran.

未审查(原文为英文):whole-file invariant review of 6 heavy files — not launched this round: live-host/src/main/daemon-connection.ts, live-host/src/main/index.ts, live-host/src/shared/protocol.ts, qwen-live/src/host/live-host-coordinator.ts, qwen-live/src/orchestrator/live-session.ts, qwen-live/src/realtime/realtime-session.ts (their fix-commit hunks were reviewed by the ledger-ruling agents, but no whole-file invariant walk ran).

未审查(原文为英文):packages/qwen-live/src/memory/** — not reviewed; round 1 disclosed the same subsystem unreviewed, so it has now gone two rounds unread.

未审查(原文为英文):build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped in CI and their suites did not run locally, and the macOS packaging/codesign half of live-host.yml needs darwin plus Xcode so it was not exercised; the 8 new integration-tests/qwen-live-* files WERE run locally (8 files / 17 tests passed after npm run bundle) even though Integration Tests (CLI, No Sandbox) was skipped in CI.

未审查(原文为英文):test-efficacy probe — harnessValidated null and all 47 probes inconclusive (the repo's vitest globalSetup build guard made the probe runner produce no parseable JSON), so no mutation-survivor or hunk-necessity evidence exists in either direction; 489 mutants and 448 hunks were skipped for cap.

未审查(原文为英文):R2-29 (host_quit_failed.kind is a constant) — a verifier incidental that no verification round ruled on; carried terminal-only at low confidence.

未探索到全部深度(达到工具调用预算):"agent invariant-c (packages/live-host/src/renderer/main.ts)"none — I finished the walk within the tool budget (~12 calls).

未审查:"agent verify"——启动 prompt 为它指定了 diff 中的行,但它从未打开:有工具调用,却没有一次读取 diff。

未审查:整个 diff、关联 issue 一致性检查、全 diff 测试覆盖检查、反框架审计、删除行为审计、提示词执行审计、跨文件一致性检查、不变量检查(状态、定时器、集合)(packages/live-host/src/main/daemon-connection.ts)、不变量检查(计数器、返回值、错误分类)(packages/live-host/src/main/daemon-connection.ts)、不变量检查(配置字段、提前返回)(packages/live-host/src/main/daemon-connection.ts)、不变量检查(状态、定时器、集合)(packages/live-host/src/main/index.ts)、不变量检查(计数器、返回值、错误分类)(packages/live-host/src/main/index.ts)、不变量检查(配置字段、提前返回)(packages/live-host/src/main/index.ts)、不变量检查(状态、定时器、集合)(packages/live-host/src/shared/protocol.ts)、不变量检查(计数器、返回值、错误分类)(packages/live-host/src/shared/protocol.ts)、不变量检查(配置字段、提前返回)(packages/live-host/src/shared/protocol.ts)、不变量检查(状态、定时器、集合)(packages/qwen-live/src/host/live-host-coordinator.ts)、不变量检查(计数器、返回值、错误分类)(packages/qwen-live/src/host/live-host-coordinator.ts)、不变量检查(配置字段、提前返回)(packages/qwen-live/src/host/live-host-coordinator.ts)、不变量检查(状态、定时器、集合)(packages/qwen-live/src/orchestrator/live-session.ts)、不变量检查(计数器、返回值、错误分类)(packages/qwen-live/src/orchestrator/live-session.ts)、不变量检查(配置字段、提前返回)(packages/qwen-live/src/orchestrator/live-session.ts)、不变量检查(状态、定时器、集合)(packages/qwen-live/src/realtime/realtime-session.ts)、不变量检查(计数器、返回值、错误分类)(packages/qwen-live/src/realtime/realtime-session.ts)、不变量检查(配置字段、提前返回)(packages/qwen-live/src/realtime/realtime-session.ts)——它的 prompt 已构建,但没有任何 agent 有记录用它启动过。

未审查:反向审计——它的 prompt 已构建,但没有 agent 用它启动——负责搜寻评审其余部分遗漏问题的这道工序,即便运行过,也缺失了 brief 承载的方法,无法作证。

未检查(工具限制,非阻断):.github/workflows/live-host.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。

Test Plan(非阻断):834 passed — this review observed 29259, 884 passed; 258 passed — this review observed 29259, 884 passed; 185 passed — this review observed 29259, 884 passed; 17 passed — this review observed 29259, 884 passed

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

— qwen3.8-max via Qwen Code /review (v0.23.1)

Comment thread packages/live-host/src/main/daemon-connection.ts Outdated
Comment thread packages/qwen-live/src/orchestrator/live-session.ts Outdated
Comment thread packages/qwen-live/src/proactive/scheduler.ts
Comment thread packages/qwen-live/src/realtime/realtime-session.test.ts Outdated
Comment thread packages/live-host/src/renderer/style.css Outdated
Comment thread packages/qwen-live/src/daemon.ts
Comment thread packages/live-host/src/main/daemon-connection.ts
Comment thread packages/live-host/src/main/__tests__/review-quit-recovery.test.ts
Comment thread packages/qwen-live/src/proactive/realtime-monitor.ts
Comment thread packages/live-host/src/main/__tests__/review-visual-readiness.test.ts Outdated
菲兹 added 2 commits September 10, 2026 15:58
…ements

# Conflicts:
#	packages/qwen-live/src/adaptor/acp-adaptor.test.ts
#	packages/qwen-live/src/adaptor/acp-adaptor.ts
@wangxiongts

Copy link
Copy Markdown
Collaborator Author

Review round 2 follow-up and accumulated local fixes

Updated feature commit: c3c7dd513994d22b9f503bef26e5e2f2c04cddcf. Integrated head: bfecd8342f51f5106172668d3603c88c164cecd7, including upstream main f6540d1994acf761ef306827c0d156013454039f. Existing PR/merge history is preserved; no force push or hook bypass.

This update also includes the requested local Subagents stop/permission controls, selected-display full-frame capture for Monitor/Live Feed, continuous output-audio resampling, native orb re-show position preservation, and debug-only visual Monitor request archives. The archive contains real sent JSON/JPEG/WAV and response data, uses private permissions, and retains the latest ten Monitor directories. Normal mode does not record Monitor media. Six newly added designs now have complete linked English/Chinese versions.

Round 2 decisions

Finding Disposition
R1-4 Fixed. Always attempt authenticated HTTP Quit. Only a matching receipt, or actual connection refusal plus ESRCH for the original PID, permits success. HTTP errors/reset/timeout and a PID probe alone do not. The original authenticated target is intentionally retained; a retry is never redirected to an unverified replacement.
R1-8 Fixed for the real Qwen join path using exact returned message IDs and injection acknowledgements/promotion IDs. Handles both event/receipt orderings, a mutable active-turn race, concurrent joins, aliases and duplicate placeholder collapse. No bare next-ref guessing: generic/older backends without either a receipt ref or exact identity signal intentionally remain unknown.
R1-28 Fixed. Capture holes reset continuity; continuous slow capture still warms, and the default single-frame path is preserved.
R2-1 Fixed. Wire PCM tests independently pin 16 kHz input / 24 kHz output. Both wrong-rate mutations fail again.
R2-2 Fixed. Reduced-motion selectors win the cascade and suppress the orb animations and microphone transform.
R1-19 Deferred deliberately. The SDK documents session_not_found/404 during temporary runtime draining/replacement as recoverable. An arbitrary retry cap or status/text-only retirement would lose live task tracking; authoritative terminal-session semantics are required.
R2-3 Fixed. Stopping is rendered in the muted gray state.
R2-4 Fixed at DOM/native hover-policy boundary. Truncated status is a hit-testable interactive title target, without adding a drag handler or enlarging the interactive surface. Native macOS tooltip appearance is not claimed verified.
R2-5 Fixed. Shared symbols connect all six validation-rule producers to the exact-match receipt allowlist; six real-dispatcher tests and producer/map drift mutations cover the path. No substring matching or raw-error exposure.
R2-6 Fixed. Unknown visualInput keys fail loudly; the supported screenDisplayId is included.
R2-7 Fixed. Cleanup warnings name the resource and bounded, credential-redacted nested causes; HTTP error text remains unchanged.
R2-8 Fixed. Failed default Memory endpoint derivation uses the established unavailable sentinel, preserving daemon setup/local memory and independent explicit Memory endpoints.
R2-9 Fixed. Signal-driven process exit releases only its own discovery lease even after cleanup failure. Retryable HTTP Quit retains its lease and shutdown control.
R2-10 Fixed. A terminal Quit latch fences late state/error/audio and reconnects after failed shared Quit, while explicit stop retry still works.
R2-11 Strengthened immediate failure, late-message, real HTTP-attempt and refusal/PID assertions. The unsafe pre-fetch arm was removed, not restored merely to retain four-arm mutation coverage.
R2-12 Fixed coverage. Multiple recycled transports retain the full evaluation budget; deleting the reset fails the new test.
R2-13 Fixed coverage. Rejection diagnostics assert the exact event name and fields.

Previously deferred schema/JPEG deduplication remains outside this correctness follow-up. The full ported Monitor prompt is preserved as requested; non-executable Func_call output remains diagnosed rather than granted tool authority.

Verification

Group Result
Qwen Live full package 1,016 passed; 2 real-backend manual tests skipped
Live Host full package 373 passed; changed upstream package-manager case also reverified after merge
Built-in Live and routes 195 passed
Server Live lifecycle/Appshot groups 37 passed
Eight Live fake-provider/ACP integration files 17 passed
Previously failing Web Shell split-render cases 2 passed

Total distinct cases in these groups: 1,640 passed, with the 2 manual cases excluded. Focused reruns and independent probes are not added to that total.

The first eight-file integration run used its configured retry once for the idle-follow-up steering case; the steering file then passed 2/2 with retries disabled. Web Shell checks on Node 25 required disabling Node's experimental Web Storage in the worker environment (NODE_OPTIONS=--no-experimental-webstorage) to let jsdom provide browser storage; no product code was changed for that test-environment mismatch. The original CI undefined-mock failure is fixed by the incorporated upstream commit.

Passed: repository build/typecheck/bundle, standalone Host build/typecheck, lockfile integrity (npm and pnpm), desktop isolation, scoped formatting/lint and whitespace checks. Pre-commit formatting/ESLint completed; the merge hook emitted an ignored-directory re-add warning, but the complete before/after staged binary patch hash was identical and that workspace subtree exactly matches upstream. Worktree is clean.

Independent verification exercised 293 compiled-artifact assertions, 12 in-memory mutation arms, exact source and built Host Quit/CSS probes, and 25 non-silent Chromium offline-audio rate/partition cases. These overlap the groups above and are additional evidence, not extra test totals. Independent bounded reviews and final self-audit found no remaining verified blocker in the accepted fixes.

This is not a whole-PR certification. Existing bundle-size/Browserslist/import-meta warnings remain visible; dependencies were reused, not clean installed. Real provider behavior, physical Bluetooth listening, native OS tooltip appearance, Windows/Linux, and the user's exact screenshot-triggered orb movement are not claimed verified. The separately reproduced native re-show position defect is verified fixed. Debug archives provide the next evidence for actual screen-condition detection.

中文摘要

已处理本轮 17 条意见:修复退出误判与失败后状态恢复、Qwen 后台任务的精确关联、视觉暖机连续性、减少动态效果、状态文字悬停、视觉配置校验、Memory 启动耦合与关停清理,并补强对应测试。不会把没有确切信号的旧后端任务猜成已完成;R1-19 的重连策略继续延期,因为 404 也可能是可恢复的运行时切换。

此次同时上传此前本地的子智能体停止/授权、完整显示器采集、音频连续性、小球位置和 debug Monitor 留档修改。合入 main 时采用上游 30 秒原生 ACP 握手超时,并保留包管理器冷启动的 5 分钟等待。

相关分组共 1,640 项通过,另有 2 项真实后端手动测试跳过;构建、类型检查、打包、锁文件与隔离检查通过。编译产物验证、变异测试和独立审查的范围及限制见上方;真实硬件/模型场景未被替代性测试宣称已完全解决。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs.

@LaZzyMan LaZzyMan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@pomelo-nwu pomelo-nwu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@pomelo-nwu

Copy link
Copy Markdown
Collaborator

@wangxiongts thanks for your contribution!

@LaZzyMan
LaZzyMan disabled auto-merge September 11, 2026 03:13
@LaZzyMan
LaZzyMan added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit f649d65 Sep 11, 2026
76 of 78 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants